import React, { createContext, useContext, useState, useCallback, useRef, ReactNode } from 'react';
import { useEditor } from '@craftjs/core';
import { PageData } from '../types';
import { SerializedTreeNode } from '../types/sitesmith';
import { useSiteDesign, SiteDesign } from './SiteDesignContext';

/** Only `Container` instances are "real" canvases in serialized state — they
 * directly render whatever is in node.data.nodes. Layout-shell components
 * (Section, HeroSimple, FeaturesGrid, ColumnLayout, CTASection, etc) use
 * Craft.js <Element canvas id="…"> linkedNodes internally; their own
 * isCanvas must be FALSE or Craft.js's toNodeTree walker trips an Invariant
 * because the shell claims to be a canvas but its render ignores `nodes`. */
const CANVAS_TYPES = new Set<string>(['Container']);

/** Shells that wrap their content in a single <Element id="<key>" is={Container}>.
 * When the AI puts content directly under one of these, the children end up
 * orphaned (the shell ignores data.nodes — it renders via the linkedNode) and
 * Craft.js auto-creates the linkedNode at render time with a botched type
 * field, which then crashes toNodeTree. Pre-create the linkedNode ourselves
 * to keep the state shape Craft.js expects. */
const SHELL_INNER: Record<string, string> = {
  Section: 'section-inner',
  BackgroundSection: 'bg-section-inner',
  FormContainer: 'form-inner',
};

interface PageContextValue {
  pages: PageData[];
  headerPage: PageData;
  footerPage: PageData;
  activePageId: string;
  isEditingHeader: boolean;
  isEditingFooter: boolean;
  switchPage: (pageId: string) => void;
  editHeader: () => void;
  editFooter: () => void;
  addPage: (name: string, slug: string) => void;
  deletePage: (pageId: string) => void;
  renamePage: (pageId: string, name: string, slug: string) => void;
  setHeaderCraftState: (craftState: string) => void;
  setFooterCraftState: (craftState: string) => void;
  setPagesCraftState: (pagesData: { id: string; name: string; slug: string; craftState: string | null }[]) => void;
  /** AI helpers — replace entire site or page with a new tree */
  replaceAllPages: (pages: { name: string; tree: SerializedTreeNode }[]) => void;
  replaceCurrentPage: (page: { name: string; tree: SerializedTreeNode }) => void;
  setHeader: (tree: SerializedTreeNode) => void;
  setFooter: (tree: SerializedTreeNode) => void;
  siteDesign: SiteDesign;
}

const HEADER_ID = '__header__';
const FOOTER_ID = '__footer__';

const EMPTY_CANVAS =
  '{"ROOT":{"type":{"resolvedName":"Container"},"isCanvas":true,"props":{"style":{"minHeight":"100vh","backgroundColor":"#ffffff"},"tag":"div"},"displayName":"Container","custom":{},"hidden":false,"nodes":[],"linkedNodes":{}}}';

const EMPTY_HEADER =
  '{"ROOT":{"type":{"resolvedName":"Container"},"isCanvas":true,"props":{"style":{"minHeight":"60px","backgroundColor":"#ffffff","padding":"12px 24px","display":"flex","alignItems":"center"},"tag":"header"},"displayName":"Container","custom":{},"hidden":false,"nodes":[],"linkedNodes":{}}}';

const EMPTY_FOOTER =
  '{"ROOT":{"type":{"resolvedName":"Container"},"isCanvas":true,"props":{"style":{"minHeight":"60px","backgroundColor":"#0f172a","color":"#94a3b8","padding":"40px 24px","textAlign":"center"},"tag":"footer"},"displayName":"Container","custom":{},"hidden":false,"nodes":[],"linkedNodes":{}}}';

// Default header seed: a ROOT header Container holding a Navbar with default
// links. New sites previously opened with an EMPTY header (just a bare
// Container), so there was no menu to edit and the empty zone rendered as a
// stray band above the page. Seeding a real Navbar gives every new site an
// editable menu-with-links out of the box (and removes the empty-header gap).
// Node shape matches serializeTreeForCraft() / Craft's actions.deserialize().
export const DEFAULT_HEADER_STATE = JSON.stringify({
  ROOT: {
    type: { resolvedName: 'Container' },
    isCanvas: true,
    props: { style: { width: '100%' }, tag: 'header' },
    displayName: 'Container',
    custom: {},
    hidden: false,
    nodes: ['header-navbar'],
    linkedNodes: {},
  },
  'header-navbar': {
    type: { resolvedName: 'Navbar' },
    isCanvas: false,
    props: {
      logoType: 'text',
      logoText: 'MySite',
      logoImage: '',
      logoWidth: '120px',
      logoUrl: '/',
      logoFontFamily: 'Inter, sans-serif',
      logoFontSize: '20px',
      links: [
        { text: 'Home', href: '/' },
        { text: 'About', href: '#about' },
        { text: 'Services', href: '#services' },
        { text: 'Contact', href: '#contact', isCta: true },
      ],
      backgroundColor: '#ffffff',
      textColor: '#3f3f46',
      hoverColor: '#3b82f6',
      ctaColor: '#3b82f6',
      ctaTextColor: '#ffffff',
      padding: '16px 24px',
      navAlignment: 'space-between',
      isSticky: false,
      showMobileMenu: false,
      style: { borderBottom: '1px solid #e4e4e7' },
    },
    displayName: 'Navbar',
    parent: 'ROOT',
    custom: {},
    hidden: false,
    nodes: [],
    linkedNodes: {},
  },
});

const PageContext = createContext<PageContextValue>({
  pages: [],
  headerPage: { id: HEADER_ID, name: 'Header', slug: '__header__', craftState: null, headCode: '' },
  footerPage: { id: FOOTER_ID, name: 'Footer', slug: '__footer__', craftState: null, headCode: '' },
  activePageId: 'home',
  isEditingHeader: false,
  isEditingFooter: false,
  switchPage: () => {},
  editHeader: () => {},
  editFooter: () => {},
  addPage: () => {},
  deletePage: () => {},
  renamePage: () => {},
  setHeaderCraftState: () => {},
  setFooterCraftState: () => {},
  setPagesCraftState: () => {},
  replaceAllPages: () => {},
  replaceCurrentPage: () => {},
  setHeader: () => {},
  setFooter: () => {},
  siteDesign: {} as SiteDesign,
});

export const usePages = () => useContext(PageContext);

function slugify(name: string): string {
  return name
    .toLowerCase()
    .trim()
    .replace(/[^a-z0-9\s-]/g, '')
    .replace(/\s+/g, '-')
    .replace(/-+/g, '-');
}

const DEFAULT_PAGE: PageData = {
  id: 'home',
  name: 'Home',
  slug: 'index',
  craftState: null,
  headCode: '',
};

const DEFAULT_HEADER: PageData = {
  id: HEADER_ID,
  name: 'Header',
  slug: '__header__',
  craftState: DEFAULT_HEADER_STATE,
  headCode: '',
};

const DEFAULT_FOOTER: PageData = {
  id: FOOTER_ID,
  name: 'Footer',
  slug: '__footer__',
  craftState: null,
  headCode: '',
};

export const PageProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
  const { query, actions } = useEditor();
  const { design } = useSiteDesign();
  const [pages, setPages] = useState<PageData[]>([DEFAULT_PAGE]);
  const [headerPage, setHeaderPage] = useState<PageData>(DEFAULT_HEADER);
  const [footerPage, setFooterPage] = useState<PageData>(DEFAULT_FOOTER);
  const [activePageId, setActivePageId] = useState('home');
  const activePageIdRef = useRef(activePageId);
  activePageIdRef.current = activePageId;

  const isEditingHeader = activePageId === HEADER_ID;
  const isEditingFooter = activePageId === FOOTER_ID;

  /** Save whatever is on the current Frame back to the right state slot */
  const saveCurrentState = useCallback(() => {
    const currentState = query.serialize();
    const currentId = activePageIdRef.current;

    if (currentId === HEADER_ID) {
      setHeaderPage((prev) => ({ ...prev, craftState: currentState }));
    } else if (currentId === FOOTER_ID) {
      setFooterPage((prev) => ({ ...prev, craftState: currentState }));
    } else {
      setPages((prev) =>
        prev.map((p) => (p.id === currentId ? { ...p, craftState: currentState } : p)),
      );
    }
  }, [query]);

  /** Load a craft state into the Frame */
  const loadState = useCallback(
    (craftState: string | null, fallback: string) => {
      setTimeout(() => {
        try {
          actions.deserialize(craftState || fallback);
        } catch (e) {
          console.error('Failed to deserialize state:', e);
          try {
            actions.deserialize(fallback);
          } catch (_e2) {
            // give up
          }
        }
      }, 0);
    },
    [actions],
  );

  const switchPage = useCallback(
    (pageId: string) => {
      if (pageId === activePageIdRef.current) return;

      // Serialize the current Craft.js state synchronously BEFORE switching
      const currentState = query.serialize();
      const currentId = activePageIdRef.current;

      // Persist the serialized state to the correct page slot
      if (currentId === HEADER_ID) {
        setHeaderPage((prev) => ({ ...prev, craftState: currentState }));
      } else if (currentId === FOOTER_ID) {
        setFooterPage((prev) => ({ ...prev, craftState: currentState }));
      } else {
        setPages((prev) =>
          prev.map((p) => (p.id === currentId ? { ...p, craftState: currentState } : p)),
        );
      }

      // Load target page state.
      // For header/footer we need the latest saved state. Since setState above is async,
      // read from the ref-like state getter. For header/footer, we read the current
      // state value and fall back to what we just saved if the target is the same slot.
      if (pageId === HEADER_ID) {
        // Use functional state read to get the latest value
        setHeaderPage((prev) => {
          loadState(prev.craftState, EMPTY_HEADER);
          return prev;
        });
      } else if (pageId === FOOTER_ID) {
        setFooterPage((prev) => {
          loadState(prev.craftState, EMPTY_FOOTER);
          return prev;
        });
      } else {
        setPages((prev) => {
          const target = prev.find((p) => p.id === pageId);
          loadState(target?.craftState || null, EMPTY_CANVAS);
          return prev;
        });
      }

      setActivePageId(pageId);
      activePageIdRef.current = pageId;
    },
    [query, loadState],
  );

  const editHeader = useCallback(() => {
    switchPage(HEADER_ID);
  }, [switchPage]);

  const editFooter = useCallback(() => {
    switchPage(FOOTER_ID);
  }, [switchPage]);

  const addPage = useCallback(
    (name: string, slug: string) => {
      const finalSlug = slug || slugify(name);
      const id = `page_${Date.now()}`;

      // Save current page first
      saveCurrentState();

      setPages((prev) => [
        ...prev,
        {
          id,
          name,
          slug: finalSlug,
          craftState: null,
          headCode: '',
        },
      ]);

      // Switch to the new page with empty canvas
      loadState(null, EMPTY_CANVAS);

      setActivePageId(id);
      activePageIdRef.current = id;
    },
    [saveCurrentState, loadState],
  );

  const deletePage = useCallback(
    (pageId: string) => {
      // Can't delete header/footer
      if (pageId === HEADER_ID || pageId === FOOTER_ID) return;

      setPages((prev) => {
        if (prev.length <= 1) return prev;

        const filtered = prev.filter((p) => p.id !== pageId);

        // If deleting the active page, switch to the first remaining
        if (pageId === activePageIdRef.current) {
          const nextPage = filtered[0];
          setActivePageId(nextPage.id);
          activePageIdRef.current = nextPage.id;
          loadState(nextPage.craftState, EMPTY_CANVAS);
        }

        return filtered;
      });
    },
    [loadState],
  );

  const renamePage = useCallback((pageId: string, name: string, slug: string) => {
    setPages((prev) =>
      prev.map((p, i) => {
        if (p.id !== pageId) return p;
        // First page is the landing page — its slug is locked to 'index' so
        // the file always publishes to index.html regardless of the user-set
        // name. The display name can change freely.
        const nextSlug = i === 0 ? 'index' : (slug || slugify(name));
        return { ...p, name, slug: nextSlug };
      }),
    );
  }, []);

  /** Allow external code (e.g., load from API) to set the header craft state */
  const setHeaderCraftState = useCallback((craftState: string) => {
    setHeaderPage((prev) => ({ ...prev, craftState }));
  }, []);

  /** Allow external code (e.g., load from API) to set the footer craft state */
  const setFooterCraftState = useCallback((craftState: string) => {
    setFooterPage((prev) => ({ ...prev, craftState }));
  }, []);

  /** Allow external code (e.g., load from API) to restore pages with craft states */
  const setPagesCraftState = useCallback((pagesData: { id: string; name: string; slug: string; craftState: string | null }[]) => {
    setPages(pagesData.map((p, i) => ({
      id: p.id,
      name: p.name,
      // Heal legacy projects whose first page was saved with slug='home' (or
      // any other) before the landing-page rule existed. The first page is
      // ALWAYS the landing page → slug 'index' → file index.html.
      slug: i === 0 ? 'index' : p.slug,
      craftState: p.craftState,
      headCode: '',
    })));
  }, []);

  /** Flatten a SerializedTreeNode into a Craft.js SerializedNodes JSON string */
  const treeToState = useCallback((tree: SerializedTreeNode): string => {
    let counter = 0;
    const nodes: Record<string, unknown> = {};
    const walk = (node: SerializedTreeNode, parent: string | null): string => {
      const id = (node.props.node_id as string | undefined) || `ai-auto-${counter++}`;
      const childIds: string[] = [];
      const typeName = node.type?.resolvedName;
      // Normalize props: the AI sometimes emits `style: []` instead of `{}`.
      // React/Craft.js choke when a CSSProperties slot is an array — normalize it.
      const rawProps = node.props ?? {};
      const props: Record<string, unknown> = { ...rawProps };
      if (Array.isArray(props.style)) props.style = {};
      nodes[id] = {
        type: node.type,
        // isCanvas must match the component's craft.rules — only layout
        // wrappers accept children. Setting it true on leaf components
        // (Heading, TextBlock, ButtonLink, etc) makes Craft.js render them
        // as empty drop-canvas wrappers and the actual content disappears.
        isCanvas: typeName ? CANVAS_TYPES.has(typeName) : false,
        props,
        displayName: typeName,
        custom: {},
        hidden: false,
        parent,
        nodes: childIds,
        linkedNodes: {},
      };
      for (const child of node.nodes ?? []) {
        childIds.push(walk(child, id));
      }
      // ColumnLayout uses Craft.js linkedNodes with fixed ids (col-0, col-1, ...).
      // The AI emits children as direct `nodes`, but ColumnLayout's render ignores
      // them and creates fresh column Elements — the AI's children become orphans
      // and any subsequent toNodeTree walk hits an Invariant. Move direct children
      // into linkedNodes so they render in the columns the user actually sees.
      if (typeName === 'ColumnLayout' && childIds.length > 0) {
        const linked: Record<string, string> = {};
        childIds.forEach((cid, i) => {
          linked[`col-${i}`] = cid;
          if (nodes[cid]) (nodes[cid] as any).isCanvas = true; // columns are canvases
        });
        (nodes[id] as any).nodes = [];
        (nodes[id] as any).linkedNodes = linked;
        // Reflect the actual column count on the component so its render matches.
        const cur = (nodes[id] as any).props || {};
        if (!cur.columns || cur.columns !== childIds.length) cur.columns = childIds.length;
        (nodes[id] as any).props = cur;
      }
      // Section/BackgroundSection/FormContainer each render a single
      // <Element id="<key>" is={Container} canvas> ... </Element>. If the AI
      // nests content as direct children, Craft.js will auto-create the
      // linkedNode on first render — and store its type as the Container
      // component class rather than {resolvedName:'Container'}, which then
      // crashes toNodeTree with "type (undefined) does not exist in resolver".
      // Pre-create the linkedNode ourselves with the correct serialized type
      // so Craft.js never has to materialize it.
      const innerKey = SHELL_INNER[typeName ?? ''];
      if (innerKey && childIds.length > 0) {
        const innerId = `${id}__${innerKey}`;
        nodes[innerId] = {
          type: { resolvedName: 'Container' },
          isCanvas: true,
          props: { tag: 'div' },
          displayName: 'Container',
          custom: {},
          hidden: false,
          parent: id,
          nodes: [...childIds],
          linkedNodes: {},
        };
        for (const cid of childIds) {
          if (nodes[cid]) (nodes[cid] as any).parent = innerId;
        }
        (nodes[id] as any).nodes = [];
        (nodes[id] as any).linkedNodes = { [innerKey]: innerId };
      }
      return id;
    };
    const rootId = walk(tree, null);
    // Craft.js deserialize requires the root node keyed as 'ROOT'
    if (rootId !== 'ROOT') {
      nodes['ROOT'] = nodes[rootId];
      (nodes['ROOT'] as any).parent = null;
      // ROOT must be a canvas regardless of component type so children render.
      (nodes['ROOT'] as any).isCanvas = true;
      delete nodes[rootId];
      // Fix up parent references from ROOT's children
      for (const childId of (nodes['ROOT'] as any).nodes) {
        if (nodes[childId]) (nodes[childId] as any).parent = 'ROOT';
      }
    }
    return JSON.stringify(nodes);
  }, []);

  /**
   * AI helper: replace all pages with newly generated trees.
   * Stores each page's serialized state without touching the live canvas
   * (the canvas still shows the currently active page — call switchPage() if needed).
   */
  const replaceAllPages = useCallback((newPages: { name: string; tree: SerializedTreeNode }[]) => {
    if (newPages.length === 0) return;
    const built = newPages.map((p, i) => ({
      id: i === 0 ? 'home' : `page_${Date.now()}_${i}`,
      name: p.name,
      // First page must publish to index.html so it serves at the site root.
      // Apache resolves '/' to index.html, not home.html — without this, the
      // AI's "Home" page lands at /home.html and visitors hit a blank root.
      slug: i === 0 ? 'index' : slugify(p.name),
      craftState: treeToState(p.tree),
      headCode: '',
    }));
    setPages(built);
    // Load the first page into the live canvas
    const firstState = built[0].craftState;
    setActivePageId(built[0].id);
    activePageIdRef.current = built[0].id;
    loadState(firstState, EMPTY_CANVAS);
  }, [treeToState, loadState]);

  /**
   * AI helper: replace the current page's tree.
   * Deserializes the new tree into the live Craft.js canvas and persists it.
   */
  const replaceCurrentPage = useCallback((page: { name: string; tree: SerializedTreeNode }) => {
    const craftState = treeToState(page.tree);
    const currentId = activePageIdRef.current;
    if (currentId === HEADER_ID) {
      setHeaderPage((prev) => ({ ...prev, name: page.name, craftState }));
    } else if (currentId === FOOTER_ID) {
      setFooterPage((prev) => ({ ...prev, name: page.name, craftState }));
    } else {
      setPages((prev) =>
        prev.map((p) => (p.id === currentId ? { ...p, name: page.name, craftState } : p)),
      );
    }
    loadState(craftState, EMPTY_CANVAS);
  }, [treeToState, loadState]);

  /**
   * AI helper: replace the shared header tree.
   * Updates stored state; does NOT switch the canvas to header view.
   */
  const setHeader = useCallback((tree: SerializedTreeNode) => {
    const craftState = treeToState(tree);
    setHeaderPage((prev) => ({ ...prev, craftState }));
    // If the canvas is currently showing the header, refresh it live
    if (activePageIdRef.current === HEADER_ID) {
      loadState(craftState, EMPTY_HEADER);
    }
  }, [treeToState, loadState]);

  /**
   * AI helper: replace the shared footer tree.
   * Updates stored state; does NOT switch the canvas to footer view.
   */
  const setFooter = useCallback((tree: SerializedTreeNode) => {
    const craftState = treeToState(tree);
    setFooterPage((prev) => ({ ...prev, craftState }));
    // If the canvas is currently showing the footer, refresh it live
    if (activePageIdRef.current === FOOTER_ID) {
      loadState(craftState, EMPTY_FOOTER);
    }
  }, [treeToState, loadState]);

  return (
    <PageContext.Provider
      value={{
        pages,
        headerPage,
        footerPage,
        activePageId,
        isEditingHeader,
        isEditingFooter,
        switchPage,
        editHeader,
        editFooter,
        addPage,
        deletePage,
        renamePage,
        setHeaderCraftState,
        setFooterCraftState,
        setPagesCraftState,
        replaceAllPages,
        replaceCurrentPage,
        setHeader,
        setFooter,
        siteDesign: design,
      }}
    >
      {children}
    </PageContext.Provider>
  );
};
