<!-- @format -->
<script lang="ts">
  import {
    listEntryTemplates,
    type EntryTemplateItemDto,
  } from "$lib/backend/templates";
  import { listFragments, type FragmentDto } from "$lib/backend/fragments";
  import { listLists, type ListDocumentDto } from "$lib/backend/lists";
  import { listTodoLists, type TodoListDto } from "$lib/backend/todos";
  import CalendarWidget from "$lib/components/CalendarWidget.svelte";
  import {
    entriesBusyStore,
    entriesStore,
    searchEntriesAsItems,
  } from "$lib/stores/entries";
  import {
    createFragmentDraft,
    fragmentsStore,
    serializeFragment,
  } from "$lib/stores/fragments";
  import {
    createListDraft,
    createListFromLabel,
    listsStore,
  } from "$lib/stores/lists";
  import {
    createTodoListDraft,
    createTodoListFromLabel,
    serializeTodoList,
    todoListsStore,
    todosStore,
  } from "$lib/stores/todos";
  import { vaultUnlocked } from "$lib/stores/session";
  import { extractEntryTags } from "$lib/utils/metadata";

  export let activeSection = "entries";
  export let activeDocumentId = "";
  export let templateRefreshToken = 0;
  export let onOpenDocument: (doc: {
    id: string;
    label: string;
    initialContent: string;
  }) => void = () => {};
  export let onEditItem: (doc: {
    id: string;
    label: string;
    initialContent: string;
  }) => void = () => {};
  export let onDeleteItem: (doc: {
    id: string;
    label: string;
  }) => void = () => {};
  export let onCalendarStateChange: (state: {
    items: SidePanelItem[];
    busy: boolean;
    error: string;
  }) => void = () => {};

  let showNewItemInput = false;
  let newItemName = "";
  let newItemInput: HTMLInputElement | null = null;
  let createTemplateMode = false;

  type SidePanelItem = {
    id: string;
    label: string;
    initialContent: string;
    sortDate?: string;
  };
  type CalendarViewMode = "day" | "week" | "month";
  type CalendarSortMode = "asc" | "desc";
  type SavedCalendarView = {
    id: string;
    name: string;
    viewMode: CalendarViewMode;
    sortMode: CalendarSortMode;
    query: string;
    tags: string;
    types: string;
    startDate: string;
    endDate: string;
  };

  let todoDocuments: SidePanelItem[] = [];
  let templateItems: SidePanelItem[] = [];
  let templatesBusy = false;
  let templateError = "";
  let wasEntriesSection = false;
  let lastTemplateRefreshToken = -1;

  const sectionTitles: Record<string, string> = {
    entries: "Entries",
    calendar: "Calendar",
    fragments: "Fragments",
    todos: "To-Do List",
    lists: "Lists",
  };

  const today = new Date();
  let calendarYear = today.getFullYear();
  let calendarMonth = today.getMonth();
  let calendarMonthLabel = new Date(
    calendarYear,
    calendarMonth,
    1,
  ).toLocaleString(undefined, {
    month: "long",
  });
  let calendarViewMode: CalendarViewMode = "month";
  let calendarSortMode: CalendarSortMode = "desc";
  let calendarQuery = "";
  let calendarTags = "";
  let calendarTypes = "";
  let calendarStartDate = "";
  let calendarEndDate = "";
  let calendarTimelineItems: SidePanelItem[] = [];
  let calendarBusy = false;
  let calendarError = "";
  let calendarSavedViews: SavedCalendarView[] = [];
  let showSaveViewInput = false;
  let saveViewName = "";
  let hasLoadedSavedViews = false;
  let calendarTimelineDebounce: ReturnType<typeof setTimeout> | null = null;
  let calendarDataVersion = 0;
  let calendarScheduleKey = "";
  let calendarRefreshRequestId = 0;
  let lastActiveSection = "";
  let calendarLastRefreshedAt = "";
  let calendarDateExplicitlySelected = false;
  const CALENDAR_SAVED_VIEWS_KEY = "journal.calendar.savedViews";

  let selectedCalendarDate: {
    year: number;
    month: number;
    day: number;
    key: string;
  } | null = {
    year: today.getFullYear(),
    month: today.getMonth(),
    day: today.getDate(),
    key: `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`,
  };

  function handleVisibleMonthChange(payload: {
    year: number;
    month: number;
    label: string;
  }) {
    calendarYear = payload.year;
    calendarMonth = payload.month;
    calendarMonthLabel = payload.label;
  }

  function toDateKey(year: number, month: number, day: number): string {
    return `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
  }

  function parseCsv(input: string): string[] {
    return input
      .split(",")
      .map((token) => token.trim())
      .filter(Boolean);
  }

  function parseDateLabel(value: string): Date | null {
    const token = value
      .trim()
      .split(/\s*[|·]\s*/)[0]
      .trim();
    if (!/^\d{4}-\d{2}-\d{2}$/.test(token)) return null;
    const date = new Date(`${token}T00:00:00`);
    return Number.isNaN(date.getTime()) ? null : date;
  }

  function parseIsoDate(value: string): Date | null {
    if (!value) return null;
    const parsed = new Date(value);
    return Number.isNaN(parsed.getTime()) ? null : parsed;
  }

  function toIsoDate(date: Date): string {
    return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
  }

  function isWithinRange(
    date: Date,
    startDate: string,
    endDate: string,
  ): boolean {
    const start = parseDateLabel(startDate);
    const end = parseDateLabel(endDate);
    if (!start || !end) return true;
    const day = toIsoDate(date);
    return day >= toIsoDate(start) && day <= toIsoDate(end);
  }

  function matchesTextQuery(content: string, query: string): boolean {
    const trimmed = query.trim();
    if (!trimmed) return true;
    return content.toLowerCase().includes(trimmed.toLowerCase());
  }

  function getActiveDateRange(): { startDate: string; endDate: string } {
    if (calendarStartDate && calendarEndDate) {
      return { startDate: calendarStartDate, endDate: calendarEndDate };
    }

    const selected = selectedCalendarDate ?? {
      year: calendarYear,
      month: calendarMonth,
      day: 1,
    };
    const selectedDate = new Date(selected.year, selected.month, selected.day);

    if (calendarViewMode === "day") {
      const date = toIsoDate(selectedDate);
      return { startDate: date, endDate: date };
    }

    if (calendarViewMode === "week") {
      const weekDay = (selectedDate.getDay() + 6) % 7;
      const start = new Date(selectedDate);
      start.setDate(selectedDate.getDate() - weekDay);
      const end = new Date(start);
      end.setDate(start.getDate() + 6);
      return { startDate: toIsoDate(start), endDate: toIsoDate(end) };
    }

    const monthStart = new Date(calendarYear, calendarMonth, 1);
    const monthEnd = new Date(calendarYear, calendarMonth + 1, 0);
    return { startDate: toIsoDate(monthStart), endDate: toIsoDate(monthEnd) };
  }

  function toFragmentTimelineItem(fragment: FragmentDto): SidePanelItem {
    const split = fragment.description.split(/\n{2,}/);
    const title = (split[0] ?? "").trim() || "Untitled Fragment";
    const body =
      split.slice(1).join("\n\n").trim() || "Add details for this fragment.";
    const date = parseIsoDate(fragment.time) ?? new Date();
    const dateKey = toIsoDate(date);
    return {
      id: `fragments/${fragment.id}`,
      label: `${dateKey} | Fragment | ${title}`,
      initialContent: serializeFragment({
        title,
        type: fragment.type,
        tags: fragment.tags ?? [],
        body,
      }),
      sortDate: date.toISOString(),
    };
  }

  function toListTimelineItem(list: ListDocumentDto): SidePanelItem {
    const created =
      parseIsoDate(list.createdAt) ??
      parseIsoDate(list.updatedAt) ??
      new Date();
    const dateKey = toIsoDate(created);
    return {
      id: `lists/${list.id}`,
      label: `${dateKey} | List | ${list.label}`,
      initialContent: list.content || `# ${list.label}\n\n`,
      sortDate: created.toISOString(),
    };
  }

  function toTodoTimelineItem(list: TodoListDto): SidePanelItem {
    const created = parseIsoDate(list.createdAt) ?? new Date();
    const dateKey = toIsoDate(created);
    const items = list.items.map((item, index) => ({
      id: Date.now() + index,
      text: item.text,
      done: item.done,
    }));
    return {
      id: `todos/${list.id}`,
      label: `${dateKey} | To-Do | ${list.label}`,
      initialContent: serializeTodoList(list.label, items),
      sortDate: created.toISOString(),
    };
  }

  function splitFilterTokens(input: string): string[] {
    return parseCsv(input).map((token) => token.toLowerCase());
  }

  function matchesAnyToken(text: string, tokens: string[]): boolean {
    if (tokens.length === 0) return true;
    const lower = text.toLowerCase();
    return tokens.some((token) => lower.includes(token));
  }

  function matchesTags(
    itemTags: string[] | undefined,
    tagTokens: string[],
  ): boolean {
    if (tagTokens.length === 0) return true;
    const normalized = (itemTags ?? [])
      .map((tag) => tag.toLowerCase().trim())
      .filter(Boolean);
    return tagTokens.some((token) => normalized.includes(token));
  }

  function scheduleCalendarRefresh(delayMs = 200) {
    if (activeSection !== "calendar") return;

    const scheduleKey = [
      selectedCalendarDate?.key ?? "",
      calendarYear,
      calendarMonth,
      calendarViewMode,
      calendarSortMode,
      calendarQuery,
      calendarTags,
      calendarTypes,
      calendarStartDate,
      calendarEndDate,
      calendarDataVersion,
    ].join("|");

    if (scheduleKey === calendarScheduleKey && calendarTimelineDebounce) {
      return;
    }

    calendarScheduleKey = scheduleKey;
    if (calendarTimelineDebounce) {
      clearTimeout(calendarTimelineDebounce);
    }

    calendarTimelineDebounce = setTimeout(() => {
      void refreshCalendarTimeline();
    }, delayMs);
  }

  async function refreshCalendarTimeline(): Promise<void> {
    const requestId = ++calendarRefreshRequestId;
    calendarBusy = true;
    calendarError = "";
    try {
      if (requestId !== calendarRefreshRequestId) return;
      if (!$vaultUnlocked) {
        calendarTimelineItems = [];
        return;
      }

      const { startDate, endDate } = getActiveDateRange();
      const entryItems = await searchEntriesAsItems({
        query: calendarQuery.trim() || undefined,
      });
      if (requestId !== calendarRefreshRequestId) return;

      const [fragmentDtos, listDtos, todoDtos] = await Promise.all([
        listFragments(),
        listLists(),
        listTodoLists(),
      ]);
      if (requestId !== calendarRefreshRequestId) return;

      const query = calendarQuery.trim();
      const tagTokens = splitFilterTokens(calendarTags);
      const typeTokens = splitFilterTokens(calendarTypes);
      const hasTypeFilter = typeTokens.length > 0;

      const fragmentItems = fragmentDtos
        .filter((fragment) => {
          const date = parseIsoDate(fragment.time);
          if (!date || !isWithinRange(date, startDate, endDate)) return false;
          if (!matchesTextQuery(fragment.description, query)) return false;
          if (!matchesTags(fragment.tags, tagTokens)) return false;
          if (
            hasTypeFilter &&
            !matchesAnyToken(fragment.type ?? "", typeTokens)
          )
            return false;
          return true;
        })
        .map(toFragmentTimelineItem);

      const listItems = listDtos.map(toListTimelineItem).filter((item) => {
        if (hasTypeFilter) return false;
        if (tagTokens.length > 0) return false;
        const date = item.sortDate ? parseIsoDate(item.sortDate) : null;
        if (!date || !isWithinRange(date, startDate, endDate)) return false;
        if (
          !matchesTextQuery(item.initialContent, query) &&
          !matchesTextQuery(item.label, query)
        )
          return false;
        return true;
      });

      const todoItems = todoDtos.map(toTodoTimelineItem).filter((item) => {
        if (hasTypeFilter) return false;
        if (tagTokens.length > 0) return false;
        const date = item.sortDate ? parseIsoDate(item.sortDate) : null;
        if (!date || !isWithinRange(date, startDate, endDate)) return false;
        if (
          !matchesTextQuery(item.initialContent, query) &&
          !matchesTextQuery(item.label, query)
        )
          return false;
        return true;
      });

      const entriesWithKind: SidePanelItem[] = entryItems
        .map((item) => {
          const date = parseDateLabel(item.label);
          const dateKey = date ? toIsoDate(date) : item.label;
          return {
            id: item.id,
            label: `${dateKey} | Entry | ${item.label}`,
            initialContent: item.initialContent,
            sortDate: date ? date.toISOString() : undefined,
          };
        })
        .filter((item) => {
          if (hasTypeFilter) return false;
          if (!matchesTags(extractEntryTags(item.initialContent), tagTokens))
            return false;
          if (
            !matchesTextQuery(item.initialContent, query) &&
            !matchesTextQuery(item.label, query)
          )
            return false;
          const date = item.sortDate ? parseIsoDate(item.sortDate) : null;
          if (date && !isWithinRange(date, startDate, endDate)) return false;
          return true;
        });

      const merged = [
        ...entriesWithKind,
        ...fragmentItems,
        ...listItems,
        ...todoItems,
      ];
      const sorted = merged.sort((a, b) => {
        const aDate = a.sortDate
          ? (parseIsoDate(a.sortDate)?.getTime() ?? 0)
          : (parseDateLabel(a.label)?.getTime() ?? 0);
        const bDate = b.sortDate
          ? (parseIsoDate(b.sortDate)?.getTime() ?? 0)
          : (parseDateLabel(b.label)?.getTime() ?? 0);
        return calendarSortMode === "asc" ? aDate - bDate : bDate - aDate;
      });
      calendarTimelineItems = sorted;
      calendarLastRefreshedAt = new Date().toLocaleTimeString(undefined, {
        hour: "2-digit",
        minute: "2-digit",
        second: "2-digit",
      });
    } catch (error) {
      if (requestId !== calendarRefreshRequestId) return;
      calendarError = String(error);
      calendarTimelineItems = [];
    } finally {
      if (requestId === calendarRefreshRequestId) {
        calendarBusy = false;
      }
    }
  }

  async function forceRefreshCalendar(options?: {
    allowWhileBusy?: boolean;
  }): Promise<void> {
    if (activeSection !== "calendar") return;
    if (calendarBusy && !options?.allowWhileBusy) return;
    if (calendarTimelineDebounce) {
      clearTimeout(calendarTimelineDebounce);
      calendarTimelineDebounce = null;
    }
    await refreshCalendarTimeline();
  }

  function loadSavedViews() {
    if (typeof window === "undefined") return;
    const raw = window.localStorage.getItem(CALENDAR_SAVED_VIEWS_KEY);
    if (!raw) {
      calendarSavedViews = [];
      return;
    }

    try {
      const parsed = JSON.parse(raw) as SavedCalendarView[];
      calendarSavedViews = Array.isArray(parsed) ? parsed : [];
    } catch {
      calendarSavedViews = [];
    }
  }

  function persistSavedViews() {
    if (typeof window === "undefined") return;
    window.localStorage.setItem(
      CALENDAR_SAVED_VIEWS_KEY,
      JSON.stringify(calendarSavedViews),
    );
  }

  function applySavedView(view: SavedCalendarView) {
    calendarViewMode = view.viewMode;
    calendarSortMode = view.sortMode;
    calendarQuery = view.query;
    calendarTags = view.tags;
    calendarTypes = view.types;
    calendarStartDate = view.startDate;
    calendarEndDate = view.endDate;
  }

  function saveCurrentView() {
    const name = saveViewName.trim();
    if (!name) {
      showSaveViewInput = false;
      saveViewName = "";
      return;
    }

    const view: SavedCalendarView = {
      id: `view-${Date.now()}`,
      name,
      viewMode: calendarViewMode,
      sortMode: calendarSortMode,
      query: calendarQuery,
      tags: calendarTags,
      types: calendarTypes,
      startDate: calendarStartDate,
      endDate: calendarEndDate,
    };
    calendarSavedViews = [view, ...calendarSavedViews];
    persistSavedViews();
    showSaveViewInput = false;
    saveViewName = "";
  }

  function deleteSavedView(id: string) {
    calendarSavedViews = calendarSavedViews.filter((view) => view.id !== id);
    persistSavedViews();
  }

  const builtInViews: SavedCalendarView[] = [
    {
      id: "builtin-week",
      name: "This Week",
      viewMode: "week",
      sortMode: "desc",
      query: "",
      tags: "",
      types: "",
      startDate: "",
      endDate: "",
    },
    {
      id: "builtin-trigger",
      name: "Trigger Review",
      viewMode: "month",
      sortMode: "desc",
      query: "",
      tags: "stress, trigger",
      types: "!TRIGGER",
      startDate: "",
      endDate: "",
    },
  ];

  function openOrCreateDailyEntry(dateKey: string) {
    const existing = $entriesStore.find(
      (item) =>
        item.label === dateKey &&
        !item.id.startsWith("entries/template-draft-"),
    );
    if (existing) {
      onOpenDocument(existing);
      return;
    }

    const draft: SidePanelItem = {
      id: `entries/draft-${Date.now()}`,
      label: dateKey,
      initialContent: `# ${dateKey}\n\n`,
    };
    entriesStore.update((items) => [draft, ...items]);
    onEditItem(draft);
  }

  function handleSelectedDateChange(payload: {
    year: number;
    month: number;
    day: number;
    key: string;
  }) {
    selectedCalendarDate = payload;
  }

  function handleDateActivate(payload: {
    year: number;
    month: number;
    day: number;
    key: string;
  }) {
    selectedCalendarDate = payload;
    if (activeSection !== "calendar") return;
    calendarDateExplicitlySelected = true;
    calendarStartDate = payload.key;
    calendarEndDate = payload.key;
  }

  function toTemplateStoreId(filePath: string): string {
    return `entries/template-file/${encodeURIComponent(filePath)}`;
  }

  function toTemplateLabel(fileName: string): string {
    return fileName.replace(/\.template\.md$/i, "");
  }

  function mapTemplateDto(item: EntryTemplateItemDto): SidePanelItem {
    return {
      id: toTemplateStoreId(item.filePath),
      label: toTemplateLabel(item.fileName),
      initialContent: "",
    };
  }

  async function refreshTemplates() {
    if (activeSection !== "entries") return;
    if (!$vaultUnlocked) {
      templatesBusy = false;
      templateError = "";
      templateItems = [];
      return;
    }
    templatesBusy = true;
    templateError = "";
    try {
      const templates = await listEntryTemplates();
      templateItems = templates.map(mapTemplateDto);
    } catch (error) {
      templateError = String(error);
      templateItems = [];
    } finally {
      templatesBusy = false;
    }
  }

  function handleAddItem() {
    if (
      activeSection === "entries" ||
      activeSection === "todos" ||
      activeSection === "lists"
    ) {
      if (activeSection === "entries") {
        createTemplateMode = false;
      }
      showNewItemInput = true;
      newItemName = "";
      queueMicrotask(() => newItemInput?.focus());
      return;
    }

    if (activeSection === "fragments") {
      onOpenDocument(createFragmentDraft());
      return;
    }

    if (activeSection === "calendar") {
      if (calendarDateExplicitlySelected) {
        const selected = selectedCalendarDate ?? {
          year: calendarYear,
          month: calendarMonth,
          day: 1,
          key: toDateKey(calendarYear, calendarMonth, 1),
        };
        openOrCreateDailyEntry(selected.key);
      } else {
        showNewItemInput = true;
        newItemName = "";
        queueMicrotask(() => newItemInput?.focus());
      }
    }
  }

  function handleRefreshClick() {
    void forceRefreshCalendar();
  }

  function handleAddTemplate() {
    if (activeSection !== "entries") return;
    createTemplateMode = true;
    showNewItemInput = true;
    newItemName = "";
    queueMicrotask(() => newItemInput?.focus());
  }

  function toDailyNoteLabel(date: Date): string {
    return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
  }

  function handleAddDailyNote() {
    if (activeSection !== "entries") return;
    createTemplateMode = false;
    showNewItemInput = false;
    newItemName = "";

    const label = toDailyNoteLabel(new Date());
    const existing = $entriesStore.find(
      (item) =>
        item.label === label && !item.id.startsWith("entries/template-draft-"),
    );
    if (existing) {
      onEditItem(existing);
      return;
    }

    const id = `entries/draft-${Date.now()}`;
    const item = { id, label, initialContent: `# ${label}\n\n` };
    entriesStore.update((items) => [item, ...items]);
    onEditItem(item);
  }

  async function confirmNewItem() {
    const label = newItemName.trim();
    if (!label) {
      cancelNewItem();
      return;
    }
    showNewItemInput = false;
    newItemName = "";

    if (activeSection === "entries") {
      const isTemplate = createTemplateMode;
      const displayLabel = isTemplate
        ? label.endsWith("_template")
          ? label
          : `${label}_template`
        : label;
      const id = isTemplate
        ? `entries/template-draft-${Date.now()}`
        : `entries/draft-${Date.now()}`;
      const item = {
        id,
        label: displayLabel,
        initialContent: `# ${displayLabel}\n\n`,
      };
      entriesStore.update((items) => [item, ...items]);
      onEditItem(item);
      createTemplateMode = false;
    } else if (activeSection === "calendar") {
      const id = `entries/draft-${Date.now()}`;
      const item = { id, label, initialContent: `# ${label}\n\n` };
      entriesStore.update((items) => [item, ...items]);
      onEditItem(item);
    } else if (activeSection === "todos") {
      try {
        const { meta, items: todoItems } = await createTodoListFromLabel(label);
        onOpenDocument({
          id: meta.id,
          label: meta.label,
          initialContent: serializeTodoList(meta.label, todoItems),
        });
      } catch (error) {
        const draft = createTodoListDraft();
        draft.meta.label = label;
        todoListsStore.update((lists) => [draft.meta, ...lists]);
        todosStore.update((lists) => ({
          ...lists,
          [draft.meta.id]: draft.items,
        }));
        onOpenDocument({
          id: draft.meta.id,
          label: draft.meta.label,
          initialContent: serializeTodoList(draft.meta.label, draft.items),
        });
      }
    } else if (activeSection === "lists") {
      try {
        const item = await createListFromLabel(label);
        onOpenDocument(item);
      } catch (error) {
        const item = createListDraft();
        item.label = label;
        item.initialContent = `# ${label}\n\n`;
        listsStore.update((items) => [item, ...items]);
        onOpenDocument(item);
      }
    }
  }

  function cancelNewItem() {
    showNewItemInput = false;
    newItemName = "";
    createTemplateMode = false;
  }

  function handleNewItemKeydown(event: KeyboardEvent) {
    if (event.key === "Enter") {
      event.preventDefault();
      confirmNewItem();
    } else if (event.key === "Escape") {
      cancelNewItem();
    }
  }

  $: panelTitle = sectionTitles[activeSection] ?? "Entries";
  $: todoDocuments = $todoListsStore.map(({ id, label }) => ({
    id,
    label,
    initialContent: serializeTodoList(label, $todosStore[id] ?? []),
  }));
  $: items =
    activeSection === "entries"
      ? $entriesStore
      : activeSection === "todos"
        ? todoDocuments
        : activeSection === "fragments"
          ? $fragmentsStore
          : activeSection === "lists"
            ? $listsStore
            : [];
  $: isCalendarSection = activeSection === "calendar";
  $: showItemActions =
    activeSection === "entries" ||
    activeSection === "todos" ||
    activeSection === "lists" ||
    activeSection === "fragments";
  $: entryItems =
    activeSection === "entries"
      ? $entriesStore.filter(
          (item) => !item.id.startsWith("entries/template-draft-"),
        )
      : [];
  $: templateDraftItems =
    activeSection === "entries"
      ? $entriesStore.filter((item) =>
          item.id.startsWith("entries/template-draft-"),
        )
      : [];
  $: allTemplateItems = [...templateDraftItems, ...templateItems];

  $: if (
    activeSection === "entries" &&
    (!wasEntriesSection || templateRefreshToken !== lastTemplateRefreshToken)
  ) {
    wasEntriesSection = true;
    lastTemplateRefreshToken = templateRefreshToken;
    void refreshTemplates();
  }

  $: if (activeSection !== "entries") {
    wasEntriesSection = false;
  }

  $: if (activeSection === "calendar" && !hasLoadedSavedViews) {
    hasLoadedSavedViews = true;
    loadSavedViews();
  }

  $: if (activeSection === "calendar") {
    // Track store mutations without building large string signatures.
    const storeInvalidationTick = [
      $entriesStore,
      $fragmentsStore,
      $listsStore,
      $todoListsStore,
      $todosStore,
    ];
    void storeInvalidationTick;
    calendarDataVersion += 1;
  }

  $: if (activeSection === "calendar") {
    const calendarTrigger = [
      selectedCalendarDate?.key ?? "",
      calendarYear,
      calendarMonth,
      calendarViewMode,
      calendarSortMode,
      calendarQuery,
      calendarTags,
      calendarTypes,
      calendarStartDate,
      calendarEndDate,
      calendarDataVersion,
    ];
    void calendarTrigger;
    scheduleCalendarRefresh(200);
  }

  $: if (activeSection === "calendar" && lastActiveSection !== "calendar") {
    calendarDateExplicitlySelected = false;
    calendarScheduleKey = "";
    void forceRefreshCalendar({ allowWhileBusy: true });
  }
  $: if (activeSection !== "calendar" && calendarTimelineDebounce) {
    clearTimeout(calendarTimelineDebounce);
    calendarTimelineDebounce = null;
  }
  $: lastActiveSection = activeSection;

  $: if (activeSection === "calendar") {
    onCalendarStateChange({
      items: calendarTimelineItems,
      busy: calendarBusy,
      error: calendarError,
    });
  }
</script>

<section class="side-panel" aria-label="Section panel">
  <header class="panel-header">
    <h2>{panelTitle}</h2>
    <div class="panel-header-actions">
      {#if activeSection === "calendar"}
        <button
          type="button"
          class="panel-action"
          aria-label="Refresh calendar"
          title="Refresh calendar"
          on:click={handleRefreshClick}
        >
          <span class="material-symbols-outlined">refresh</span>
        </button>
      {/if}
      {#if activeSection === "entries"}
        <button
          type="button"
          class="panel-action"
          aria-label="Add template"
          title="Add template"
          on:click={handleAddTemplate}
        >
          <span class="material-symbols-outlined">palette</span>
        </button>
        <button
          type="button"
          class="panel-action"
          aria-label="Add daily note"
          title="Add daily note"
          on:click={handleAddDailyNote}
        >
          <span class="material-symbols-outlined">calendar_month</span>
        </button>
      {/if}
      <button
        type="button"
        class="panel-action"
        aria-label="Add item"
        title="Add item"
        on:click={handleAddItem}
      >
        <span class="material-symbols-outlined">add</span>
      </button>
    </div>
  </header>

  {#if isCalendarSection}
    {#if showNewItemInput}
      <div class="new-item-input">
        <input
          type="text"
          bind:this={newItemInput}
          bind:value={newItemName}
          placeholder="Entry title..."
          on:keydown={handleNewItemKeydown}
          on:blur={confirmNewItem}
        />
      </div>
    {/if}

    <div class="calendar-controls">
      <div class="calendar-control-row">
        <label>
          View
          <select bind:value={calendarViewMode}>
            <option value="day">Day</option>
            <option value="week">Week</option>
            <option value="month">Month</option>
          </select>
        </label>
        <label>
          Sort
          <select bind:value={calendarSortMode}>
            <option value="desc">Newest</option>
            <option value="asc">Oldest</option>
          </select>
        </label>
      </div>

      <div class="calendar-control-row">
        <label>
          Start
          <input type="date" bind:value={calendarStartDate} />
        </label>
        <label>
          End
          <input type="date" bind:value={calendarEndDate} />
        </label>
      </div>

      <div class="calendar-control-row">
        <input
          type="text"
          bind:value={calendarQuery}
          placeholder="Text query"
        />
        <input
          type="text"
          bind:value={calendarTags}
          placeholder="Tags (comma)"
        />
      </div>

      <div class="calendar-control-row">
        <input
          type="text"
          bind:value={calendarTypes}
          placeholder="Fragment types (comma)"
        />
        <span></span>
      </div>

      <div class="calendar-saved-views">
        <h3>Saved Views</h3>
        <div class="saved-view-actions">
          {#each builtInViews as view}
            <button
              type="button"
              class="saved-view-btn"
              on:click={() => applySavedView(view)}>{view.name}</button
            >
          {/each}
          <button
            type="button"
            class="saved-view-btn"
            on:click={() => (showSaveViewInput = true)}>Save Current</button
          >
        </div>
        {#if showSaveViewInput}
          <div class="saved-view-input">
            <input
              type="text"
              bind:value={saveViewName}
              placeholder="View name"
              on:keydown={(event) => {
                if (event.key === "Enter") saveCurrentView();
                if (event.key === "Escape") {
                  showSaveViewInput = false;
                  saveViewName = "";
                }
              }}
              on:blur={saveCurrentView}
            />
          </div>
        {/if}
        {#if calendarSavedViews.length > 0}
          <ul class="saved-view-list">
            {#each calendarSavedViews as view}
              <li>
                <button
                  type="button"
                  class="saved-view-btn"
                  on:click={() => applySavedView(view)}>{view.name}</button
                >
                <button
                  type="button"
                  class="saved-view-delete"
                  on:click={() => deleteSavedView(view.id)}
                  aria-label="Delete saved view"
                >
                  <span class="material-symbols-outlined">delete</span>
                </button>
              </li>
            {/each}
          </ul>
        {/if}
      </div>
    </div>

    <CalendarWidget
      onVisibleMonthChange={handleVisibleMonthChange}
      onSelectedDateChange={handleSelectedDateChange}
      onDateActivate={handleDateActivate}
    />

    <div class="calendar-entries">
      <h3>{calendarMonthLabel} {calendarYear} Timeline</h3>
      <p class="section-copy">
        Last refreshed: {calendarLastRefreshedAt || "Not yet"}
      </p>
    </div>
  {:else}
    <div class="panel-search">
      <span class="material-symbols-outlined">search</span>
      <input
        type="text"
        placeholder={`Search ${panelTitle.toLowerCase()}...`}
      />
    </div>

    {#if showNewItemInput}
      <div class="new-item-input">
        <input
          type="text"
          bind:this={newItemInput}
          bind:value={newItemName}
          placeholder={activeSection === "entries"
            ? createTemplateMode
              ? "Template name..."
              : "Entry name..."
            : activeSection === "todos"
              ? "Todo list name..."
              : "List name..."}
          on:keydown={handleNewItemKeydown}
          on:blur={confirmNewItem}
        />
      </div>
    {/if}

    {#if activeSection === "entries"}
      <div class="panel-subsection">
        <h3>Entries</h3>
        {#if $entriesBusyStore}
          <p class="section-copy">Loading entries...</p>
        {:else}
          <ul class="panel-list">
            {#each entryItems as item}
              <li class:is-active={item.id === activeDocumentId}>
                <button
                  type="button"
                  class="item-label"
                  on:click={() => onOpenDocument(item)}
                >
                  {item.label}
                </button>
                <div class="item-actions">
                  <button
                    type="button"
                    class="item-action"
                    on:click|stopPropagation={() => onEditItem(item)}
                    aria-label="Edit"
                  >
                    <span class="material-symbols-outlined">edit</span>
                  </button>
                  <button
                    type="button"
                    class="item-action item-action-danger"
                    on:click|stopPropagation={() => onDeleteItem(item)}
                    aria-label="Delete"
                  >
                    <span class="material-symbols-outlined">delete</span>
                  </button>
                </div>
              </li>
            {/each}
          </ul>
          {#if !entryItems.length}
            <p class="section-copy">No entries found.</p>
          {/if}
        {/if}
      </div>

      <div class="panel-subsection">
        <h3>Templates</h3>
        {#if templatesBusy}
          <p class="section-copy">Loading templates...</p>
        {:else}
          <ul class="panel-list">
            {#each allTemplateItems as item}
              <li class:is-active={item.id === activeDocumentId}>
                <button
                  type="button"
                  class="item-label"
                  on:click={() => onOpenDocument(item)}
                >
                  {item.label}
                </button>
                <div class="item-actions">
                  <button
                    type="button"
                    class="item-action"
                    on:click|stopPropagation={() => onEditItem(item)}
                    aria-label="Edit"
                  >
                    <span class="material-symbols-outlined">edit</span>
                  </button>
                  <button
                    type="button"
                    class="item-action item-action-danger"
                    on:click|stopPropagation={() => onDeleteItem(item)}
                    aria-label="Delete"
                  >
                    <span class="material-symbols-outlined">delete</span>
                  </button>
                </div>
              </li>
            {/each}
          </ul>
          {#if !allTemplateItems.length}
            <p class="section-copy">No templates found.</p>
          {/if}
        {/if}
        {#if templateError}
          <p class="template-error">{templateError}</p>
        {/if}
      </div>
    {:else}
      <ul class="panel-list">
        {#each items as item}
          <li class:is-active={item.id === activeDocumentId}>
            <button
              type="button"
              class="item-label"
              on:click={() => onOpenDocument(item)}
            >
              {item.label}
            </button>
            {#if showItemActions}
              <div class="item-actions">
                <button
                  type="button"
                  class="item-action"
                  on:click|stopPropagation={() => onEditItem(item)}
                  aria-label="Edit"
                >
                  <span class="material-symbols-outlined">edit</span>
                </button>
                <button
                  type="button"
                  class="item-action item-action-danger"
                  on:click|stopPropagation={() => onDeleteItem(item)}
                  aria-label="Delete"
                >
                  <span class="material-symbols-outlined">delete</span>
                </button>
              </div>
            {/if}
          </li>
        {/each}
      </ul>
    {/if}
  {/if}
</section>

<style>
  .side-panel {
    background: linear-gradient(
      180deg,
      var(--surface-2) 0%,
      var(--bg-panel) 100%
    );
    border-right: 1px solid var(--border-soft);
    padding: 16px 14px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-height: 0;
    overflow: hidden;
  }

  .panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }

  .panel-header-actions {
    display: flex;
    align-items: center;
    gap: 6px;
  }

  .panel-header h2 {
    font-size: 0.95rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--text-primary);
  }

  .panel-action {
    width: 28px;
    height: 28px;
    display: grid;
    place-items: center;
    border-radius: 6px;
    color: var(--text-muted);
    cursor: pointer;
    border: 1px solid transparent;
  }

  .panel-action:hover {
    background: var(--bg-hover);
    border-color: var(--border-soft);
    color: var(--text-primary);
  }

  .panel-action .material-symbols-outlined {
    font-size: 1.05rem;
  }

  .panel-search {
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--surface-1);
    border: 1px solid var(--border-soft);
    border-radius: 8px;
    padding: 7px 9px;
  }

  .panel-search .material-symbols-outlined {
    color: var(--text-dim);
    font-size: 1rem;
  }

  .panel-search input {
    width: 100%;
    font-size: 0.84rem;
    color: var(--text-primary);
  }

  .panel-search input::placeholder {
    color: var(--text-dim);
  }

  .panel-list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-height: 0;
    overflow: auto;

    li {
      display: flex;
      align-items: center;
      border-radius: 7px;
      border: 1px solid transparent;

      &:hover {
        background: var(--bg-hover);
        border-color: var(--border-soft);
      }

      &.is-active {
        background: var(--bg-active);
        border-color: var(--border-strong);
      }
    }

    .item-label {
      flex: 1;
      min-width: 0;
      text-align: left;
      padding: 7px 9px;
      font-size: 0.84rem;
      color: var(--text-muted);
      cursor: pointer;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    li:hover .item-label,
    li.is-active .item-label {
      color: var(--text-primary);
    }

    .item-actions {
      display: none;
      flex-shrink: 0;
      align-items: center;
      gap: 2px;
      padding-right: 4px;
    }

    li:hover .item-actions,
    li.is-active .item-actions {
      display: flex;
    }

    .item-action {
      width: 24px;
      height: 24px;
      display: grid;
      place-items: center;
      border-radius: 4px;
      color: var(--text-dim);
      cursor: pointer;
      border: 1px solid transparent;

      &:hover {
        background: var(--surface-2);
        color: var(--text-primary);
        border-color: var(--border-soft);
      }

      &.item-action-danger:hover {
        color: #e06c75;
      }

      .material-symbols-outlined {
        font-size: 0.85rem;
      }
    }
  }

  .calendar-entries {
    margin-top: 2px;
    display: flex;
    flex-direction: column;
    gap: 6px;
  }

  .calendar-controls {
    display: flex;
    flex-direction: column;
    gap: 8px;
    border: 1px solid var(--border-soft);
    border-radius: 10px;
    padding: 8px;
    background: var(--surface-1);
  }

  .calendar-control-row {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 6px;
  }

  .calendar-control-row label {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 0.72rem;
    color: var(--text-muted);
  }

  .calendar-control-row input,
  .calendar-control-row select {
    width: 100%;
    border: 1px solid var(--border-soft);
    border-radius: 7px;
    background-color: var(--surface-2);
    color: var(--text-primary);
    padding: 6px 8px;
    font-size: 0.78rem;
  }

  .calendar-control-row select {
    padding-right: 30px;
  }

  .calendar-saved-views {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }

  .calendar-saved-views h3 {
    font-size: 0.76rem;
    font-weight: 600;
    color: var(--text-muted);
    letter-spacing: 0.01em;
  }

  .saved-view-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
  }

  .saved-view-btn {
    border: 1px solid var(--border-soft);
    border-radius: 7px;
    padding: 5px 8px;
    font-size: 0.74rem;
    color: var(--text-muted);
    background: var(--surface-2);
    cursor: pointer;
  }

  .saved-view-btn:hover {
    color: var(--text-primary);
    border-color: var(--border-strong);
    background: var(--bg-hover);
  }

  .saved-view-input input {
    width: 100%;
    border: 1px solid var(--border-soft);
    border-radius: 7px;
    background: var(--surface-2);
    color: var(--text-primary);
    padding: 6px 8px;
    font-size: 0.78rem;
  }

  .saved-view-list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 4px;
  }

  .saved-view-list li {
    display: flex;
    align-items: center;
    gap: 6px;
  }

  .saved-view-delete {
    width: 24px;
    height: 24px;
    display: grid;
    place-items: center;
    border-radius: 6px;
    border: 1px solid transparent;
    color: var(--text-dim);
    background: transparent;
    cursor: pointer;
  }

  .saved-view-delete:hover {
    color: #e06c75;
    border-color: var(--border-soft);
    background: var(--bg-hover);
  }

  .calendar-entries h3 {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--text-muted);
    letter-spacing: 0.01em;
  }

  .panel-subsection {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-height: 0;
  }

  .panel-subsection h3 {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--text-muted);
    letter-spacing: 0.01em;
  }

  .section-copy {
    color: var(--text-dim);
    font-size: 0.78rem;
  }

  .template-error {
    color: #e74c3c;
    font-size: 0.78rem;
  }

  .new-item-input {
    padding: 0 2px;

    input {
      width: 100%;
      font-size: 0.84rem;
      color: var(--text-primary);
      background: var(--surface-1);
      border: 1px solid var(--border-strong);
      border-radius: 7px;
      padding: 7px 9px;
      outline: none;

      &:focus {
        border-color: var(--accent, #6b8afd);
      }

      &::placeholder {
        color: var(--text-dim);
      }
    }
  }

  @media (max-width: 980px) {
    .side-panel {
      padding: 12px 10px;
    }

    .panel-list .item-actions {
      display: flex;
    }

    .panel-list .item-action {
      width: 28px;
      height: 28px;
    }
  }

  @media (max-width: 720px) {
    .calendar-control-row {
      grid-template-columns: 1fr;
    }
  }
</style>
