/* global React */
// Sliding-indent tree: opening deeper pulls the ancestors LEFT (they compress
// and align at the gutter) so the deepest open level always keeps the same
// modest indent — and thus the same generous text width. Collapsing reverses it.

const { useState } = React;

const T = {
  paper: "#f3eee2", paper2: "#e6dfc9", surface: "#fbf8ef",
  ink: "#1c1814", ink2: "#3a342c", muted: "#6c655a",
  hairline: "#d8cfba", hairline2: "#c5b99c",
  accent: "#6a1f23", accentInk: "#fbf8ef", accentSoft: "#f0d8d4",
  serif: '"Newsreader", Georgia, serif',
  sans: '"Geist", ui-sans-serif, system-ui, sans-serif',
  mono: '"Geist Mono", ui-monospace, Menlo, monospace',
};

const LV = ["Fonds", "Series", "Subseries", "Group", "Subgroup", "File", "Subfile", "Item group", "Item", "Component"];

// chosen lineage + siblings at each level
const SPINE = [
  { node: "Mira K. Adesanya Coastal Adaptation Photographic Archive", chosen: 0,
    sibs: ["Field Studies and On-Site Documentation", "Press & Media Coverage", "Maps, Plans & Surveys", "Oral Histories"] },
  { node: "Field Studies and On-Site Documentation", chosen: 0,
    sibs: ["Europe — Low Countries", "North America — Gulf & Eastern Seaboard", "West Africa", "Asia-Pacific Deltas"] },
  { node: "Europe — Low Countries", chosen: 0, sibs: ["Netherlands", "Belgium", "Luxembourg"] },
  { node: "Netherlands", chosen: 0, sibs: ["Rotterdam Metropolitan Region", "Amsterdam", "Nijmegen", "Dordrecht"] },
  { node: "Rotterdam Metropolitan Region", chosen: 0, sibs: ["Maeslantkering Storm Surge Barrier", "Boompjes Embankment", "Katendrecht Walkway"] },
  { node: "Maeslantkering Storm Surge Barrier", chosen: 0, sibs: ["1998 Commissioning and Acceptance Testing", "2014 Decadal Stress Test", "2021 Structural Inspection"] },
  { node: "1998 Commissioning and Acceptance Testing", chosen: 0, sibs: ["Roll 14 — Second Full-Closure Stress Test", "Roll 15 — Public Observation Crowd", "Contact Sheet A"] },
  { node: "Roll 14 — Second Full-Closure Stress Test", chosen: 1, sibs: ["Frame 21 — Northern Gate Approach", "Frame 22 — Northern Gate Sealing Sequence", "Frame 23 — Barrier Fully Sealed"] },
  { node: "Frame 22 — Northern Gate Sealing Sequence", chosen: 1, sibs: ["Recto — Silver gelatin print, full frame", "Verso — Engineer's annotation in pencil", "Conservation note — surface abrasion", "Rights & reproduction statement"] },
  { node: "Verso — Engineer's annotation in pencil", chosen: -1, sibs: [] },
];
const N = 10;

const INDENT = 26;   // px per level (generous so the effect is visible)
const GUTTER = 12;
const WINDOW = 3;    // how many indent steps are preserved near the deepest level

const Tri = ({ open }) => (<svg viewBox="0 0 12 12" width="9" height="9" style={{ transform: open ? "rotate(90deg)" : "none", transition: "transform .2s" }}><path d="M4 2l5 4-5 4z" fill="currentColor" /></svg>);
const Folder = ({ s = 14 }) => (<svg viewBox="0 0 24 24" width={s} height={s} fill="none" stroke="currentColor" strokeWidth="1.4"><path d="M3 7h6l2 2h10v10H3z" /></svg>);
const FolderOpen = ({ s = 14 }) => (<svg viewBox="0 0 24 24" width={s} height={s} fill="none" stroke="currentColor" strokeWidth="1.4"><path d="M3 7h6l2 2h10v3H3z" /><path d="M3 12h17l-2 7H3z" /></svg>);
const Doc = ({ s = 14 }) => (<svg viewBox="0 0 24 24" width={s} height={s} fill="none" stroke="currentColor" strokeWidth="1.4"><path d="M6 3h9l4 4v14H6z" /><path d="M14 3v5h5" /></svg>);
const SC = ({ children, style }) => (<span style={{ fontFamily: T.sans, fontSize: 9, letterSpacing: "0.13em", textTransform: "uppercase", fontWeight: 600, color: T.muted, ...style }}>{children}</span>);

const ROW_H = 32;

function SlidingTree({ openMax, setOpenMax }) {
  // pan grows once we're deeper than WINDOW, sliding ancestors left.
  const pan = Math.max(0, (openMax - WINDOW) * INDENT);
  const xFor = (depth) => GUTTER + Math.max(0, depth * INDENT - pan);
  const onGutter = (depth) => (depth * INDENT - pan) <= 0;

  // Flat list of absolutely-positioned rows (no nesting → indent fully controlled).
  const items = [];
  for (let d = 0; d <= openMax; d++) items.push({ kind: "node", d });
  const deepest = SPINE[openMax];
  const deepLeaf = openMax === N - 1;
  if (!deepLeaf) deepest.sibs.forEach((c, i) => items.push({ kind: "child", d: openMax + 1, i, name: c, isPath: i === deepest.chosen }));

  let y = 0;
  const rows = items.map((it) => {
    const top = y; y += ROW_H;
    if (it.kind === "node") {
      const d = it.d;
      const s = SPINE[d];
      const leaf = d === N - 1;
      const active = d === openMax;
      return (
        <Row key={`n${d}`} top={top} left={xFor(d)} active={active} gutter={onGutter(d)}
          icon={leaf ? "doc" : (d < openMax ? "open" : "folder")} hasTri={!leaf} triOpen={d < openMax}
          tag={LV[d]} label={s.node} weight={active ? 600 : 500}
          onClick={() => setOpenMax(d)} />
      );
    }
    const childLeaf = it.d === N - 1;
    return (
      <Row key={`c${it.i}`} top={top} left={xFor(it.d)} active={false} gutter={onGutter(it.d)}
        icon={childLeaf ? "doc" : "folder"} hasTri={!childLeaf} triOpen={false}
        label={it.name} weight={400} cta={it.isPath ? "open ↓" : null}
        onClick={() => { if (it.isPath) setOpenMax(it.d); }} />
    );
  });

  return (
    <div style={{ height: "100%", display: "flex", flexDirection: "column" }}>
      <div style={{ padding: "10px 14px", borderBottom: `1px solid ${T.hairline}`, display: "flex", alignItems: "center", gap: 10 }}>
        <SC style={{ color: T.accent }}>Depth L{openMax + 1} / {N}</SC>
        <div style={{ marginLeft: "auto", display: "flex", gap: 6 }}>
          <button onClick={() => setOpenMax(Math.max(0, openMax - 1))} style={btn}>− up</button>
          <button onClick={() => setOpenMax(Math.min(N - 1, openMax + 1))} style={btnDark}>down ↓</button>
        </div>
      </div>
      <div style={{ flex: 1, overflow: "auto", padding: "6px 0", position: "relative" }}>
        <div style={{ position: "absolute", left: GUTTER, top: 0, bottom: 0, width: 1, background: pan > 0 ? T.hairline2 : "transparent", transition: "background .3s" }} />
        <div style={{ position: "relative", height: y }}>{rows}</div>
      </div>
      <div style={{ padding: "9px 14px", borderTop: `1px solid ${T.hairline}`, background: T.paper2 }}>
        <div style={{ fontSize: 11, color: T.muted, lineHeight: 1.45 }}>
          Past L{WINDOW + 1}, ancestors slide left and align at the gutter — the open level keeps a fixed indent, so its
          long title always has room. Step the depth up/down to feel it.
        </div>
      </div>
    </div>
  );
}

function Row({ top, left, active, gutter, icon, hasTri, triOpen, tag, label, weight, cta, onClick }) {
  const col = active ? T.accent : (icon === "doc" ? T.muted : "#9a6b35");
  return (
    <div onClick={onClick} style={{
      position: "absolute", top, left: 0, right: 0, height: ROW_H,
      display: "flex", alignItems: "center", gap: 7,
      paddingLeft: left, paddingRight: 10,
      background: active ? T.accentSoft : "transparent",
      borderLeft: active ? `2px solid ${T.accent}` : "2px solid transparent",
      color: active ? T.accent : T.ink2,
      cursor: "default", whiteSpace: "nowrap",
      transition: "padding-left .34s cubic-bezier(.4,0,.2,1), background .2s",
    }}>
      <span style={{ width: 10, color: T.muted, flexShrink: 0, opacity: gutter ? 0.3 : 1 }}>{hasTri ? <Tri open={triOpen} /> : null}</span>
      <span style={{ color: col, flexShrink: 0 }}>{icon === "doc" ? <Doc s={13} /> : icon === "open" ? <FolderOpen s={13} /> : <Folder s={13} />}</span>
      <span style={{ fontSize: 12.5, fontWeight: weight, overflow: "hidden", textOverflow: "ellipsis" }}>{label}</span>
      {cta && <span style={{ marginLeft: "auto", fontFamily: T.mono, fontSize: 8, color: T.accent, border: `1px solid ${T.accent}`, padding: "0 4px", flexShrink: 0 }}>{cta}</span>}
      {!cta && tag && <span style={{ marginLeft: "auto", fontFamily: T.mono, fontSize: 8.5, color: T.hairline2, flexShrink: 0, paddingLeft: 8 }}>{tag}</span>}
    </div>
  );
}

const btn = { background: "transparent", border: `1px solid ${T.hairline2}`, color: T.ink2, padding: "4px 10px", fontFamily: T.sans, fontSize: 11.5, fontWeight: 500, cursor: "default" };
const btnDark = { ...btn, background: T.ink, color: T.paper, border: 0 };

function App() {
  const [openMax, setOpenMax] = useState(2);
  const cur = SPINE[openMax];
  return (
    <div style={{ height: "100vh", display: "flex", flexDirection: "column", background: T.paper2, fontFamily: T.sans, color: T.ink }}>
      <div style={{ height: 52, background: T.paper, borderBottom: `1px solid ${T.hairline}`, display: "flex", alignItems: "center", padding: "0 18px", gap: 12, flexShrink: 0 }}>
        <span style={{ width: 26, height: 26, background: T.ink, color: T.paper, display: "grid", placeItems: "center", fontFamily: T.serif, fontWeight: 600, fontSize: 13 }}>E</span>
        <span style={{ fontFamily: T.serif, fontWeight: 600, fontSize: 15 }}>Eastbrook Libraries</span>
        <span style={{ color: T.muted, fontSize: 12.5 }}>· Sliding-indent finding aid</span>
      </div>
      <div style={{ flex: 1, display: "grid", gridTemplateColumns: "380px 1fr", minHeight: 0 }}>
        <aside style={{ borderRight: `1px solid ${T.hairline}`, minHeight: 0, background: T.surface, overflow: "hidden" }}>
          <SlidingTree openMax={openMax} setOpenMax={setOpenMax} />
        </aside>
        <main style={{ background: T.paper2, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 14, padding: 40, textAlign: "center" }}>
          <div style={{ width: 240, height: 176, background: "linear-gradient(135deg,#1f3a2e,#406b51 60%,#d6e4d8)" }} />
          <div style={{ maxWidth: 460 }}>
            <SC style={{ color: T.accent }}>{LV[openMax]} · level {openMax + 1} of {N}</SC>
            <div style={{ fontFamily: T.serif, fontSize: 23, fontWeight: 500, lineHeight: 1.2, marginTop: 6, textWrap: "balance" }}>{cur.node}</div>
          </div>
        </main>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
