// Column-stack workspace store (Finder-style miller columns).
//
// State is a single ordered list `columns: Column[]` rendered left-to-right
// in the main workspace. The first column is always a top-level section
// (Concepts / Model / Requirements / Assumptions / Risks / Inconsistencies);
// subsequent columns are entity details that drill down from the previous
// column's selection.
//
// Invariants:
//   - Only one top-level section is open at a time. Clicking a different
//     section in the LeftSidebar replaces the entire stack.
//   - Pushing a child at parentIndex truncates everything to its right first.
//   - Resizing a column persists per (kind, id) so the user's chosen widths
//     stick across pushes.
//
// localStorage persistence keeps just the open section + per-column widths.
// Detail children are deliberately ephemeral — they don't survive a reload.

"use client";

import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";

export type SectionPaneId =
  | "concepts"
  | "model"
  | "requirements"
  | "assumptions"
  | "risks"
  | "inconsistencies";

export const SECTION_TITLES: Record<SectionPaneId, string> = {
  concepts: "Concepts",
  model: "Model",
  requirements: "Requirements",
  assumptions: "Assumptions",
  risks: "Risks",
  inconsistencies: "Inconsistencies",
};

export type Column =
  | { kind: "section"; id: SectionPaneId }
  | { kind: "term"; id: string }
  | { kind: "block"; id: string }
  | { kind: "association"; id: string }
  | { kind: "constraint"; id: string }
  | { kind: "requirement"; id: string }
  | { kind: "finding"; id: string };

export function columnKey(c: Column): string {
  return `${c.kind}:${c.id}`;
}

export const PANE_MIN_WIDTH = 240;
export const PANE_MAX_WIDTH = 720;
export const PANE_DEFAULT_WIDTH = 340;

interface OpenPanesValue {
  /** Ordered chain of columns rendered in the main workspace. */
  columns: Column[];
  /** True when the first column is this section (the chain is rooted here). */
  isSectionOpen(id: SectionPaneId): boolean;
  /** Sidebar click: open this section as the chain root, or close everything
   *  if it's already the active root. */
  toggleSection(id: SectionPaneId): void;
  /** Force-open a section (no-op if already root). */
  openSection(id: SectionPaneId): void;
  /** Truncate everything past `parentIndex`, then push `child`. If the same
   *  child already sits at parentIndex+1, this is a no-op (clicking the
   *  active item shouldn't flicker). */
  pushFrom(parentIndex: number, child: Column): void;
  /** Drop columns from `index` onward. */
  closeFrom(index: number): void;
  /** Replace the entire stack (used for cross-section jumps, e.g. a chip
   *  click that wants to land in Concepts → term). */
  setStack(columns: Column[]): void;
  /** Per-column width, keyed by columnKey. */
  widthFor(c: Column): number;
  setWidth(c: Column, width: number): void;
}

const Ctx = createContext<OpenPanesValue | null>(null);

interface PersistShape {
  rootSection?: SectionPaneId | null;
  widths?: Record<string, number>;
  /** Legacy field from the pre-stack store. Hydrated and discarded. */
  open?: string[];
}

interface ProviderProps {
  projectId: string;
  initial?: SectionPaneId | null;
  children: React.ReactNode;
}

export function OpenPanesProvider({ projectId, initial = null, children }: ProviderProps) {
  const storageKey = `socrata.workspace.${projectId}`;
  const [columns, setColumns] = useState<Column[]>(initial ? [{ kind: "section", id: initial }] : []);
  const [widths, setWidths] = useState<Record<string, number>>({});
  const [hydrated, setHydrated] = useState(false);

  // Hydrate from localStorage on mount.
  useEffect(() => {
    try {
      const raw = window.localStorage.getItem(storageKey);
      if (raw) {
        const parsed = JSON.parse(raw) as PersistShape | string[];
        let nextRoot: SectionPaneId | null = null;
        const cleanedWidths: Record<string, number> = {};

        if (Array.isArray(parsed)) {
          // Very old format: array of section ids. Take the first valid one.
          for (const id of parsed.map(migrateLegacyId).filter(isSectionPaneId)) {
            nextRoot = id;
            break;
          }
        } else if (parsed && typeof parsed === "object") {
          if (parsed.rootSection && isSectionPaneId(parsed.rootSection)) {
            nextRoot = parsed.rootSection;
          } else if (Array.isArray(parsed.open)) {
            // Pre-stack store kept multiple sections open; pick the first valid one.
            for (const id of parsed.open.map(migrateLegacyId).filter(isSectionPaneId)) {
              nextRoot = id;
              break;
            }
          }
          if (parsed.widths && typeof parsed.widths === "object") {
            for (const [k, v] of Object.entries(parsed.widths)) {
              if (typeof v === "number" && Number.isFinite(v)) cleanedWidths[k] = clamp(v);
            }
          }
        }
        if (nextRoot) setColumns([{ kind: "section", id: nextRoot }]);
        setWidths(cleanedWidths);
      }
    } catch {
      /* ignore */
    }
    setHydrated(true);
  }, [storageKey]);

  // Persist root section + widths (NOT child columns — those are ephemeral).
  useEffect(() => {
    if (!hydrated) return;
    try {
      const root = columns[0]?.kind === "section" ? columns[0].id : null;
      const payload: PersistShape = { rootSection: root, widths };
      window.localStorage.setItem(storageKey, JSON.stringify(payload));
    } catch {
      /* ignore */
    }
  }, [columns, widths, storageKey, hydrated]);

  const isSectionOpen = useCallback(
    (id: SectionPaneId) => columns[0]?.kind === "section" && columns[0].id === id,
    [columns]
  );

  const toggleSection = useCallback((id: SectionPaneId) => {
    setColumns(cur => {
      if (cur[0]?.kind === "section" && cur[0].id === id) return [];
      return [{ kind: "section", id }];
    });
  }, []);

  const openSection = useCallback((id: SectionPaneId) => {
    setColumns(cur => {
      if (cur[0]?.kind === "section" && cur[0].id === id) return cur;
      return [{ kind: "section", id }];
    });
  }, []);

  const pushFrom = useCallback((parentIndex: number, child: Column) => {
    setColumns(cur => {
      const head = cur.slice(0, parentIndex + 1);
      const existing = cur[parentIndex + 1];
      // Same child already there → keep it (no flicker, preserves any state).
      if (existing && existing.kind === child.kind && existing.id === child.id) {
        return cur.slice(0, parentIndex + 2);
      }
      return [...head, child];
    });
  }, []);

  const closeFrom = useCallback((index: number) => {
    setColumns(cur => cur.slice(0, Math.max(0, index)));
  }, []);

  const setStack = useCallback((next: Column[]) => {
    setColumns(next);
  }, []);

  const widthFor = useCallback(
    (c: Column) => widths[columnKey(c)] ?? PANE_DEFAULT_WIDTH,
    [widths]
  );
  const setWidth = useCallback((c: Column, width: number) => {
    setWidths(prev => ({ ...prev, [columnKey(c)]: clamp(width) }));
  }, []);

  const value = useMemo<OpenPanesValue>(
    () => ({
      columns,
      isSectionOpen,
      toggleSection,
      openSection,
      pushFrom,
      closeFrom,
      setStack,
      widthFor,
      setWidth,
    }),
    [columns, isSectionOpen, toggleSection, openSection, pushFrom, closeFrom, setStack, widthFor, setWidth]
  );

  return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
}

export function useOpenPanes(): OpenPanesValue {
  const ctx = useContext(Ctx);
  if (!ctx) throw new Error("useOpenPanes must be inside <OpenPanesProvider>");
  return ctx;
}

function isSectionPaneId(x: unknown): x is SectionPaneId {
  return typeof x === "string" && x in SECTION_TITLES;
}

function migrateLegacyId(id: string): string {
  if (id === "taxonomy" || id === "glossary") return "concepts";
  return id;
}

function clamp(w: number): number {
  return Math.max(PANE_MIN_WIDTH, Math.min(PANE_MAX_WIDTH, Math.round(w)));
}
