<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useBackgroundRefresh } from '@/composables/useBackgroundRefresh'
import { useChatStore } from '@/stores/chat'
import ChatPanel from '@/components/ChatPanel.vue'
import WeatherCard from '@/components/WeatherCard.vue'
import { RotateCcw } from 'lucide-vue-next'
import {
  apiGet,
  apiPost,
  getJournalToday,
  getJournalDay,
  getJournalDays,
  triggerJournalPrep,
  listEvents,
  type EventEntry,
} from '@/api/client'

interface WeatherDay {
  day: string
  condition: string
  high: number
  low: number
  precip_probability: number | null
  precip_mm: number | null
  windspeed_max: number
}
interface WeatherData {
  location: string
  fetched_at: string
  current_temp: number
  condition: string
  today_high: number | null
  today_low: number | null
  yesterday_high: number | null
  yesterday_low: number | null
  wind_unit?: string
  forecast: WeatherDay[]
}
interface CurrentConditions {
  temperature: number | null
  windspeed: number | null
  description: string
  precip_next_3h: number[]
  temp_unit: string
  location: string
}

const chatStore = useChatStore()

// ── Day picker + conversation state ──────────────────────────────────────────
const days = ref<string[]>([])
const todayDate = ref<string | null>(null)
const selectedDay = ref<string | null>(null)
const dayConvId = ref<number | null>(null)
const isToday = computed(() => selectedDay.value !== null && selectedDay.value === todayDate.value)

// ── Weather panel ────────────────────────────────────────────────────────────
const weatherData = ref<WeatherData[]>([])
const selectedWeatherIdx = ref(0)
const tempUnit = ref<string>('C')
const currentConditions = ref<CurrentConditions | null>(null)
let currentWeatherTimer: ReturnType<typeof setInterval> | null = null

async function loadCurrentConditions() {
  try {
    currentConditions.value = await apiGet<CurrentConditions>('/api/journal/weather/current')
    if (currentConditions.value?.temperature != null && weatherData.value.length > 0) {
      weatherData.value[0] = { ...weatherData.value[0], current_temp: currentConditions.value.temperature }
    }
  } catch { /* silent */ }
}

async function loadWeather() {
  try {
    const data = await apiGet<{ locations: WeatherData[]; temp_unit: string }>('/api/journal/weather')
    weatherData.value = data.locations ?? []
    tempUnit.value = data.temp_unit ?? 'C'
  } catch { /* silent */ }
}

const refreshingWeather = ref(false)
async function refreshWeather() {
  refreshingWeather.value = true
  try {
    const data = await apiPost<{ locations: WeatherData[]; temp_unit: string }>('/api/journal/weather/refresh', {})
    weatherData.value = data.locations ?? []
    tempUnit.value = data.temp_unit ?? 'C'
  } catch { /* silent */ }
  finally { refreshingWeather.value = false }
}

// ── Upcoming events ──────────────────────────────────────────────────────────
const upcomingEvents = ref<EventEntry[]>([])

interface GroupedDay {
  label: string
  dateKey: string
  events: EventEntry[]
}

const groupedEvents = computed<GroupedDay[]>(() => {
  const groups = new Map<string, EventEntry[]>()
  const today = new Date()
  today.setHours(0, 0, 0, 0)
  for (const ev of upcomingEvents.value) {
    const d = new Date(ev.start_dt)
    const key = d.toISOString().slice(0, 10)
    if (!groups.has(key)) groups.set(key, [])
    groups.get(key)!.push(ev)
  }
  const result: GroupedDay[] = []
  for (const [key, events] of groups) {
    const d = new Date(key + 'T00:00:00')
    const diff = Math.round((d.getTime() - today.getTime()) / 86_400_000)
    let label: string
    if (diff === 0) label = 'Today'
    else if (diff === 1) label = 'Tomorrow'
    else label = d.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' })
    result.push({ label, dateKey: key, events })
  }
  return result
})

function formatEventTime(ev: EventEntry): string {
  if (ev.all_day) return 'All day'
  const d = new Date(ev.start_dt)
  return d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })
}

async function loadEvents() {
  try {
    // Window: today 00:00 → 14 days out. Matches the prep's framing — events
    // earlier today (already past) still surface here, grouped under "Today",
    // so the right rail aligns with what the prep references.
    const start = new Date()
    start.setHours(0, 0, 0, 0)
    const end = new Date(start)
    end.setDate(end.getDate() + 14)
    end.setHours(23, 59, 59, 999)
    upcomingEvents.value = await listEvents(start.toISOString(), end.toISOString())
  } catch { /* silent */ }
}

// ── Day load + switch ────────────────────────────────────────────────────────
async function loadDay(iso: string) {
  const payload = iso === todayDate.value ? await getJournalToday() : await getJournalDay(iso)
  if (payload.conversation) {
    dayConvId.value = payload.conversation.id
    await chatStore.fetchConversation(payload.conversation.id)
  } else {
    dayConvId.value = null
  }
}

async function loadAll() {
  try {
    const today = await getJournalToday()
    todayDate.value = today.day_date
    selectedDay.value = today.day_date
    if (today.conversation) {
      dayConvId.value = today.conversation.id
      await chatStore.fetchConversation(today.conversation.id)
    }
  } catch { /* silent */ }

  try {
    days.value = await getJournalDays()
    if (todayDate.value && !days.value.includes(todayDate.value)) {
      days.value = [todayDate.value, ...days.value]
    }
  } catch { /* silent */ }

  await Promise.all([loadWeather(), loadCurrentConditions(), loadEvents()])
}

watch(selectedDay, async (iso) => {
  if (!iso || iso === todayDate.value) return
  try { await loadDay(iso) } catch { /* silent */ }
})

// ── Manual prep regeneration ─────────────────────────────────────────────────
const triggering = ref(false)
async function triggerPrep() {
  if (triggering.value) return
  triggering.value = true
  try {
    await triggerJournalPrep()
    if (_mounted) await loadAll()
  } finally {
    if (_mounted) triggering.value = false
  }
}

function dayLabel(iso: string): string {
  if (iso === todayDate.value) return 'Today'
  const d = new Date(iso + 'T00:00:00')
  return d.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' })
}

const todayBadge = computed(() => {
  return new Date().toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' })
})

// ── Background refresh ───────────────────────────────────────────────────────
async function _backgroundRefreshMessages() {
  try {
    if (!_mounted || !isToday.value || !dayConvId.value) return
    await chatStore.fetchConversation(dayConvId.value)
  } catch { /* silent */ }
}

useBackgroundRefresh(
  _backgroundRefreshMessages,
  60_000,
  () => !chatStore.streaming && isToday.value && !!dayConvId.value,
)

let _mounted = true
onUnmounted(() => {
  _mounted = false
  if (currentWeatherTimer) clearInterval(currentWeatherTimer)
})

onMounted(async () => {
  await loadAll()
  currentWeatherTimer = setInterval(loadCurrentConditions, 30 * 60 * 1000)
})
</script>

<template>
  <div class="journal-root">
    <div class="journal-shell">
      <header class="journal-header">
        <div class="journal-header-left">
          <h1 class="journal-title">Journal</h1>
          <span class="journal-today-badge">{{ todayBadge }}</span>
        </div>
        <div class="journal-header-right">
          <select v-if="days.length" v-model="selectedDay" class="journal-day-select">
            <option v-for="d in days" :key="d" :value="d">{{ dayLabel(d) }}</option>
          </select>
          <button
            class="btn-trigger"
            @click="triggerPrep"
            :disabled="triggering || !isToday"
            title="Regenerate today's daily prep"
          >{{ triggering ? '…' : 'Refresh prep' }}</button>
        </div>
      </header>

      <!-- Center: chat (prep is the first assistant message inside) -->
      <div class="journal-center">
        <ChatPanel
          variant="full"
          briefingMode
          :readOnly="!isToday"
          placeholder="Tell your journal…"
          class="journal-chat-panel"
        />
      </div>

      <!-- Right: Weather + Events + News -->
      <div class="journal-right">
        <div class="weather-section" v-if="weatherData.length">
          <div class="weather-section-header">
            <div class="weather-tabs" v-if="weatherData.length > 1">
              <button
                v-for="(loc, i) in weatherData"
                :key="(loc as WeatherData).location"
                class="weather-tab"
                :class="{ active: selectedWeatherIdx === i }"
                @click="selectedWeatherIdx = i"
              >{{ (loc as WeatherData).location }}</button>
            </div>
            <button
              class="weather-refresh-btn"
              :class="{ spinning: refreshingWeather }"
              :disabled="refreshingWeather"
              @click="refreshWeather"
              title="Refresh weather"
            >
              <RotateCcw :size="16" />
            </button>
          </div>
          <WeatherCard
            :weather="weatherData[selectedWeatherIdx]"
            :temp-unit="tempUnit"
          />
        </div>

        <div class="events-section" v-if="groupedEvents.length">
          <div class="panel-label-row">
            <div class="panel-label">Upcoming</div>
            <router-link to="/calendar" class="events-cal-link">Calendar →</router-link>
          </div>
          <div class="events-list">
            <div v-for="group in groupedEvents" :key="group.dateKey" class="events-day-group">
              <div class="events-day-label">{{ group.label }}</div>
              <div v-for="ev in group.events" :key="ev.id" class="event-row">
                <span class="event-dot" :style="ev.color ? { background: ev.color } : {}"></span>
                <span class="event-body">
                  <span class="event-title">{{ ev.title }}</span>
                  <span class="event-time">{{ formatEventTime(ev) }}</span>
                  <span v-if="ev.location" class="event-loc">{{ ev.location }}</span>
                </span>
              </div>
            </div>
          </div>
        </div>

      </div>
    </div>
  </div>
</template>

<style scoped>
.journal-root {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

.journal-shell {
  display: grid;
  grid-template-columns: 1fr minmax(320px, 35%);
  grid-template-rows: auto 1fr;
  height: 100%;
  min-height: 0;
}

.journal-header {
  grid-column: 1 / -1;
  grid-row: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.25rem 1rem 1rem;
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
  gap: 1rem;
  flex-wrap: wrap;
}
.journal-header-left { display: flex; align-items: baseline; gap: 0.75rem; }
.journal-title {
  /* h1 inherits Fraunces from theme.css; weight 500 follows the doc's "two weights only" rule */
  font-size: 1.3rem;
  margin: 0;
  color: var(--color-text);
}
.journal-today-badge { font-size: 0.82rem; color: var(--color-text-muted); }
.journal-header-right { display: flex; align-items: center; gap: 0.5rem; }

.journal-day-select {
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-bg-card);
  color: var(--color-text);
  font-size: 0.82rem;
  cursor: pointer;
  font-family: inherit;
}

.btn-trigger {
  padding: 0.35rem 0.8rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-bg-card);
  color: var(--color-text-muted);
  font-size: 0.8rem;
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.15s;
  font-family: inherit;
}
.btn-trigger:hover:not(:disabled) { border-color: var(--color-primary); color: var(--color-primary); }
.btn-trigger:disabled { opacity: 0.5; cursor: not-allowed; }

/* ── Center column: prep card + chat ──────────────────────────────────────── */
.journal-center {
  grid-column: 1;
  grid-row: 2;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.journal-chat-panel { flex: 1; min-height: 0; }

/* ── Right column ─────────────────────────────────────────────────────────── */
.journal-right {
  grid-column: 2;
  grid-row: 2;
  border-left: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.weather-section { flex-shrink: 0; padding: 1rem 1rem 0.5rem; }
.weather-section :deep(.weather-card) { margin-bottom: 0; }
.weather-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.5rem;
}

.weather-refresh-btn {
  background: none;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  color: var(--color-text-muted);
  font-size: 1rem;
  cursor: pointer;
  padding: 0.2rem 0.45rem;
  line-height: 1;
  transition: all 0.15s;
}
.weather-refresh-btn:hover { border-color: var(--color-primary); color: var(--color-primary); }
.weather-refresh-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.weather-refresh-btn.spinning { animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

.weather-tabs { display: flex; gap: 0.25rem; }
.weather-tab {
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: none;
  color: var(--color-text-muted);
  font-size: 0.78rem;
  font-family: inherit;
  cursor: pointer;
  transition: all 0.15s;
}
.weather-tab:hover { border-color: var(--color-primary); color: var(--color-primary); }
.weather-tab.active {
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
  border-color: var(--color-primary);
  color: var(--color-primary);
  font-weight: 500;
}

.events-section { padding: 0.75rem 1rem; border-top: 1px solid var(--color-border); }
.events-cal-link { font-size: 0.75rem; color: var(--color-text-muted); text-decoration: none; }
.events-cal-link:hover { color: var(--color-primary); }
.events-list { display: flex; flex-direction: column; gap: 0.6rem; }
.events-day-group { display: flex; flex-direction: column; gap: 0.2rem; }
.events-day-label {
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.event-row { display: flex; align-items: flex-start; gap: 0.4rem; padding: 0.25rem 0.4rem; border-radius: 6px; }
.event-row:hover { background: color-mix(in srgb, var(--color-primary) 6%, transparent); }
.event-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--color-primary);
  flex-shrink: 0; margin-top: 5px;
}
.event-body { display: flex; flex-direction: column; gap: 0.05rem; min-width: 0; }
.event-title { font-size: 0.82rem; font-weight: 500; color: var(--color-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.event-time { font-size: 0.72rem; color: var(--color-text-muted); }
.event-loc { font-size: 0.7rem; color: var(--color-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.panel-label {
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-primary);
  flex-shrink: 0;
}
.panel-label-row { display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }

@media (max-width: 900px) {
  .journal-shell { grid-template-columns: 1fr; grid-template-rows: auto 1fr auto; }
  .journal-center { grid-column: 1; grid-row: 2; }
  .journal-right {
    grid-column: 1; grid-row: 3;
    border-left: none;
    border-top: 1px solid var(--color-border);
    max-height: 300px;
  }
}
</style>
