// Company context — Task 8. Holds the list of companies (GET /api/companies,
// which itself needs no X-Company-Id — it's the admin-pool list that powers
// the switcher, per server/src/companies.ts) and which one is currently
// selected. Selection persists to localStorage so a reload keeps the same
// company (design spec: "company switcher (persists to localStorage,
// sets X-Company-Id)").

import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode,
} from "react";
import { apiGet, ApiError } from "./api";

const STORAGE_KEY = "selectedCompanyId";

export interface Company {
  id: string;
  name: string;
  createdAt: string;
  entryCount: number;
  isSample: boolean;
}

interface CompanyContextValue {
  companies: Company[];
  /** null: nothing selected (or nothing selectable) yet. */
  selectedCompanyId: string | null;
  /** Convenience lookup of the full record for selectedCompanyId, if any. */
  selectedCompany: Company | null;
  loading: boolean;
  error: string | null;
  selectCompany: (id: string) => void;
  /** Resolves once the refetched list has actually landed in state — see
   * the comment on its implementation below for why that matters. */
  refresh: () => Promise<void>;
}

const CompanyContext = createContext<CompanyContextValue | null>(null);

function readStoredId(): string | null {
  try {
    return window.localStorage.getItem(STORAGE_KEY);
  } catch {
    return null; // localStorage can throw (private browsing, disabled storage) — never fatal
  }
}

function writeStoredId(id: string): void {
  try {
    window.localStorage.setItem(STORAGE_KEY, id);
  } catch {
    // Best-effort only — selection still works for the rest of this session.
  }
}

export function CompanyProvider({ children }: { children: ReactNode }) {
  const [companies, setCompanies] = useState<Company[]>([]);
  const [selectedCompanyId, setSelectedCompanyId] = useState<string | null>(readStoredId);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  // `load` is shared by the mount effect below and refresh() further down —
  // a plain Promise-returning callback rather than the effect+reloadToken
  // indirection this hook used before Task 10, because the setup wizard
  // needs to AWAIT a refetch actually completing (see refresh()'s comment).
  // CompanyProvider is mounted once for the app's entire lifetime (main.tsx
  // never unmounts it), so the unmount-cancellation guard this codebase
  // otherwise uses for one-shot effects (e.g. lib/useApiGet.ts) has nothing
  // to protect against here — there's no teardown for a stale response to
  // race against.
  const load = useCallback(async (): Promise<void> => {
    setLoading(true);
    setError(null);
    try {
      const data = await apiGet<{ companies: Company[] }>("/api/companies");
      setCompanies(data.companies);
    } catch (err) {
      setError(err instanceof ApiError ? err.message : "Couldn't load your companies.");
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    void load();
  }, [load]);

  // Auto-select: if the stored/current selection no longer refers to a real
  // company (or nothing was ever selected) and there's exactly one company,
  // pick it automatically (design spec: "if one, auto-select").
  useEffect(() => {
    if (loading) return;
    const stillValid = companies.some((c) => c.id === selectedCompanyId);
    if (stillValid) return;
    if (companies.length === 1) {
      const only = companies[0]!.id;
      setSelectedCompanyId(only);
      writeStoredId(only);
    } else if (selectedCompanyId !== null) {
      setSelectedCompanyId(null);
    }
  }, [companies, loading, selectedCompanyId]);

  const selectCompany = useCallback((id: string) => {
    setSelectedCompanyId(id);
    writeStoredId(id);
  }, []);

  // Refetches and resolves only once the new list has landed in state.
  // Task 10's setup wizard relies on this ordering: creating a company and
  // immediately selecting it must `await refresh()` BEFORE calling
  // selectCompany() (see setup/SetupWizard.tsx's handleNameSubmit) —
  // otherwise the auto-select effect above, which snaps the selection back
  // to the sole company whenever the current selection isn't in `companies`
  // yet, can observe the NEW selection against the STILL-STALE (pre-refetch)
  // companies array in the same render pass and silently revert it. Existing
  // fire-and-forget callers (e.g. App.tsx's error-banner retry) are
  // unaffected — they never awaited the old reloadToken-based version either.
  const refresh = useCallback((): Promise<void> => load(), [load]);

  const selectedCompany = useMemo(
    () => companies.find((c) => c.id === selectedCompanyId) ?? null,
    [companies, selectedCompanyId],
  );

  const value = useMemo<CompanyContextValue>(
    () => ({ companies, selectedCompanyId, selectedCompany, loading, error, selectCompany, refresh }),
    [companies, selectedCompanyId, selectedCompany, loading, error, selectCompany, refresh],
  );

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

export function useCompany(): CompanyContextValue {
  const ctx = useContext(CompanyContext);
  if (!ctx) throw new Error("useCompany() must be used inside <CompanyProvider>");
  return ctx;
}
