/* global React, ReactDOM */
const { useState, useEffect, useMemo, useRef } = React;

const DATA = window.SITE_DATA;

/* ── Icons (inline SVG, currentColor, stroke 1.75, round caps) ─────────────── */
function IconSun() {
    return (
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
            <circle cx="12" cy="12" r="4"></circle>
            <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"></path>
        </svg>
    );
}
function IconMoon() {
    return (
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
            <path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"></path>
        </svg>
    );
}
function IconArrow({ style }) {
    return (
        <svg className="arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={style}>
            <path d="M5 12h14M13 5l7 7-7 7"></path>
        </svg>
    );
}
function IconBack() {
    return (
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <path d="M19 12H5M12 19l-7-7 7-7"></path>
        </svg>
    );
}

/* ── Theme ─────────────────────────────────────────────────────────────────── */
function useTheme() {
    const getSystem = () => (window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark");
    const [theme, setTheme] = useState(() => {
        return localStorage.getItem("site-theme") || getSystem();
    });
    useEffect(() => {
        document.documentElement.setAttribute("data-theme", theme);
        localStorage.setItem("site-theme", theme);
    }, [theme]);
    useEffect(() => {
        const mq = window.matchMedia("(prefers-color-scheme: light)");
        const handler = () => {
            if (!localStorage.getItem("site-theme-user-set")) {
                setTheme(mq.matches ? "light" : "dark");
            }
        };
        mq.addEventListener("change", handler);
        return () => mq.removeEventListener("change", handler);
    }, []);
    const toggle = () => {
        localStorage.setItem("site-theme-user-set", "1");
        setTheme(t => (t === "dark" ? "light" : "dark"));
    };
    return [theme, toggle];
}

/* ── Router (hash) ─────────────────────────────────────────────────────────── */
function useRoute() {
    const parse = () => {
        const h = window.location.hash.slice(1) || "/";
        return h;
    };
    const [route, setRoute] = useState(parse);
    useEffect(() => {
        const onHash = () => {
            setRoute(parse());
            window.scrollTo({ top: 0, behavior: "instant" in window ? "instant" : "auto" });
        };
        window.addEventListener("hashchange", onHash);
        return () => window.removeEventListener("hashchange", onHash);
    }, []);
    const navigate = (to) => { window.location.hash = to; };
    return [route, navigate];
}

/* ── Shell ─────────────────────────────────────────────────────────────────── */
function Topbar({ route, theme, onToggleTheme }) {
    const [scrolled, setScrolled] = useState(false);
    useEffect(() => {
        const onScroll = () => setScrolled(window.scrollY > 8);
        onScroll();
        window.addEventListener("scroll", onScroll, { passive: true });
        return () => window.removeEventListener("scroll", onScroll);
    }, []);

    const isActive = (path) => {
        if (path === "/" && route === "/") return true;
        if (path !== "/" && route.startsWith(path)) return true;
        return false;
    };

    return (
        <header className={"topbar" + (scrolled ? " is-scrolled" : "")}>
            <div className="topbar__inner">
                <a href="#/" className="brand" aria-label="Home">
                    <span className="brand__mark">⌘</span>
                    <span>alexs<span style={{opacity: 0.4}}>.bio</span></span>
                </a>
                <nav className="nav">
                    <a href="#/" className={isActive("/") ? "is-active" : ""}>Home</a>
                    <a href="#/projects" className={isActive("/projects") ? "is-active" : ""}>Projects</a>
                    <a href="#/writing" className={isActive("/writing") ? "is-active" : ""}>Writing</a>
                    <a href="#/about" className={"hide-sm " + (isActive("/about") ? "is-active" : "")}>About</a>
                    <button className="theme-toggle" onClick={onToggleTheme} aria-label="Toggle theme" title={theme === "dark" ? "Switch to light" : "Switch to dark"}>
                        {theme === "dark" ? <IconSun/> : <IconMoon/>}
                    </button>
                </nav>
            </div>
        </header>
    );
}

function Footer() {
    return (
        <footer className="footer">
            <div className="footer__inner">
                <div className="footer__links">
                    <a href={"https://" + DATA.author.links.github} target="_blank" rel="noreferrer">github</a>
                    <a href={"https://discord.com/users/" + DATA.author.discord} target="_blank" rel="noreferrer">discord</a>
                    <a href="#/about">about</a>
                    <a href={DATA.author.links.rss}>rss</a>
                </div>
                <div className="footer__meta">© {new Date().getFullYear()} {DATA.author.name}</div>
            </div>
        </footer>
    );
}

/* ── Motifs (generative SVG per project, on-brand) ─────────────────────────── */
function Motif({ id }) {
    const motifs = {
        "utility-belt": (
            <svg viewBox="0 0 320 160" preserveAspectRatio="xMidYMid slice">
                {[...Array(8)].map((_, i) => (
                    <circle key={i} cx="160" cy="80" r={10 + i * 10} strokeWidth="1" opacity={0.9 - i * 0.1}/>
                ))}
                <circle className="accent-fill" cx="160" cy="80" r="4"/>
            </svg>
        ),
        "paper-trail": (
            <svg viewBox="0 0 320 160" preserveAspectRatio="xMidYMid slice">
                {[...Array(14)].map((_, i) => (
                    <line key={i} x1={20 + i * 22} y1="20" x2={20 + i * 22} y2={40 + Math.sin(i * 0.8) * 20 + 60} strokeWidth="1.25"/>
                ))}
                <line className="accent" x1="130" y1="140" x2="240" y2="30" strokeWidth="1.5"/>
            </svg>
        ),
        "glyph": (
            <svg viewBox="0 0 320 160" preserveAspectRatio="xMidYMid slice">
                <text x="160" y="118" textAnchor="middle" fontFamily="Inter 28pt" fontSize="140" fontWeight="600" fill="none" stroke="currentColor" strokeWidth="1.25" style={{color: "var(--text-secondary)"}}>Aa</text>
                <text x="160" y="118" textAnchor="middle" fontFamily="Inter 28pt" fontSize="140" fontWeight="600" className="accent-fill" opacity="0.18">Aa</text>
            </svg>
        ),
        "sonder": (
            <svg viewBox="0 0 320 160" preserveAspectRatio="xMidYMid slice">
                {[...Array(6)].map((_, i) => (
                    <g key={i} transform={`translate(${40 + i * 42} ${30 + (i % 2) * 40})`}>
                        <rect x="0" y="0" width="34" height="44" strokeWidth="1"/>
                    </g>
                ))}
                <rect className="accent" x="166" y="70" width="34" height="44" strokeWidth="1.5"/>
            </svg>
        ),
        "noctua": (
            <svg viewBox="0 0 320 160" preserveAspectRatio="xMidYMid slice">
                {[...Array(60)].map((_, i) => {
                    const x = 20 + (i % 20) * 14;
                    const y = 30 + Math.floor(i / 20) * 40;
                    const on = [13, 27, 34, 41, 52].includes(i);
                    return <circle key={i} cx={x} cy={y} r={on ? 3 : 1.5} className={on ? "accent-fill" : ""} fill={on ? undefined : "currentColor"} style={{color: on ? undefined : "var(--text-muted)"}} stroke="none"/>;
                })}
            </svg>
        ),
        "marginalia": (
            <svg viewBox="0 0 320 160" preserveAspectRatio="xMidYMid slice">
                {[...Array(7)].map((_, i) => (
                    <line key={i} x1="40" y1={26 + i * 18} x2={i === 3 ? 180 : 240} y2={26 + i * 18} strokeWidth="1.25"/>
                ))}
                <line className="accent" x1="190" y1="80" x2="260" y2="80" strokeWidth="1.75"/>
                <circle className="accent-fill" cx="266" cy="80" r="2.5"/>
            </svg>
        ),
    };
    return motifs[id] || motifs["utility-belt"];
}

/* ── Project art ───────────────────────────────────────────────────────────── */
function ProjectArt({ project }) {
    return (
        <div className={"art art--" + project.artVariant}>
            <div className="art__grid"/>
            <div className="art__motif"><Motif id={project.id}/></div>
            {project.status === "active" && <div className="halo-dot" style={{top: 14, right: 14, zIndex: 3}}/>}
            <div className="art__accent">{project.accent}</div>
        </div>
    );
}

function ProjectCard({ project, onClick }) {
    const onMove = (e) => {
        const r = e.currentTarget.getBoundingClientRect();
        e.currentTarget.style.setProperty("--mx", ((e.clientX - r.left) / r.width * 100) + "%");
        e.currentTarget.style.setProperty("--my", ((e.clientY - r.top) / r.height * 100) + "%");
    };
    return (
        <article className="project-card reveal" onClick={onClick} role="link" tabIndex={0}
                 onMouseMove={onMove}
                 onKeyDown={(e) => { if (e.key === "Enter") onClick(); }}>
            <div className="project-card__art">
                <ProjectArt project={project}/>
            </div>
            <div className="project-card__body">
                <div className="project-card__tags">
                    {project.tags.map((t, i) => (
                        <span key={t} className={"tag" + (i === 0 ? " is-accent" : "")}>{t}</span>
                    ))}
                </div>
                <h3 className="project-card__title">{project.title}</h3>
                <p className="project-card__desc">{project.description}</p>
                <div className="project-card__meta">
                    <span className="year">{project.year}</span>
                    <span>{project.status}</span>
                </div>
            </div>
        </article>
    );
}

/* ── Pages ─────────────────────────────────────────────────────────────────── */
function Home() {
    const featured = DATA.projects.slice(0, 3);
    const recentPosts = DATA.posts.slice(0, 4);
    return (
        <div className="page">
            <section className="hero">
                <div className="hero__eyebrow">
                    <span className="dot"/>
                    <span>Available for conversations</span>
                </div>
                <h1 className="hero__title">
                    Software engineer<br/>
                    <em>building web apps & games.</em>
                </h1>
                <p className="hero__sub">
                    I'm {DATA.author.name} — a software engineer based in {DATA.author.location}. I make web apps and games using high-level AI tools, and occasionally write down what I'm doing and what I'm up to.
                </p>
                <div className="hero__ctas">
                    <a href="#/projects" className="btn btn--accent">See projects <IconArrow/></a>
                    <a href="#/writing" className="btn">Read writing <IconArrow/></a>
                </div>
            </section>

            <section className="section reveal">
                <div className="section__head">
                    <div>
                        <div className="section__eyebrow">Selected work</div>
                        <h2 className="section__title">Things I've built</h2>
                    </div>
                    <a href="#/projects" className="section__link">All projects <IconArrow/></a>
                </div>
                <div className="projects-grid">
                    {featured.map(p => (
                        <ProjectCard key={p.id} project={p} onClick={() => { window.location.hash = "#/projects"; }}/>
                    ))}
                </div>
            </section>

            <section className="section reveal">
                <div className="section__head">
                    <div>
                        <div className="section__eyebrow">Recent writing</div>
                        <h2 className="section__title">Notes & essays</h2>
                    </div>
                    <a href="#/writing" className="section__link">All writing <IconArrow/></a>
                </div>
                <div className="writing-list">
                    {recentPosts.map((p, i) => (
                        <a key={p.id} href={"#/writing/" + p.id} className="writing-row reveal">
                            <div className="writing-row__date">{p.dateDisplay}</div>
                            <div>
                                <h3 className="writing-row__title">{p.title}</h3>
                                <p className="writing-row__excerpt">{p.excerpt}</p>
                            </div>
                            <div className="writing-row__meta">{p.readTime}</div>
                        </a>
                    ))}
                </div>
            </section>
        </div>
    );
}

function ProjectsPage() {
    const allTags = useMemo(() => {
        const set = new Set();
        DATA.projects.forEach(p => p.tags.forEach(t => set.add(t)));
        return ["All", ...Array.from(set)];
    }, []);
    const [active, setActive] = useState("All");

    const counts = useMemo(() => {
        const c = { All: DATA.projects.length };
        allTags.slice(1).forEach(t => {
            c[t] = DATA.projects.filter(p => p.tags.includes(t)).length;
        });
        return c;
    }, [allTags]);

    const filtered = useMemo(() => {
        if (active === "All") return DATA.projects;
        return DATA.projects.filter(p => p.tags.includes(active));
    }, [active]);

    return (
        <div className="page">
            <div className="section__eyebrow">Projects</div>
            <h1 className="section__title" style={{fontSize: "clamp(34px, 4.4vw, 48px)", marginBottom: 12}}>
                Everything I've shipped.
            </h1>
            <p style={{color: "var(--text-secondary)", fontSize: 16.5, maxWidth: 580, margin: "0 0 40px", textWrap: "pretty"}}>
                Small tools, research prototypes, weekend experiments. Some are live, some I've put down. All of them taught me something.
            </p>

            <div className="filters" role="tablist">
                {allTags.map(t => (
                    <button key={t}
                            className={"filter-chip" + (active === t ? " is-active" : "")}
                            onClick={() => setActive(t)}
                            role="tab"
                            aria-selected={active === t}>
                        {t}
                        <span className="filter-chip__count">{counts[t]}</span>
                    </button>
                ))}
            </div>

            <div className="projects-grid">
                {filtered.map(p => (
                    <ProjectCard key={p.id} project={p} onClick={() => {}}/>
                ))}
            </div>
            {filtered.length === 0 && (
                <div style={{padding: 60, textAlign: "center", color: "var(--text-muted)"}}>
                    Nothing here yet — try another tag.
                </div>
            )}
        </div>
    );
}

function WritingPage() {
    return (
        <div className="page">
            <div className="section__eyebrow">Writing</div>
            <h1 className="section__title" style={{fontSize: "clamp(34px, 4.4vw, 48px)", marginBottom: 12}}>
                Notes, essays, research.
            </h1>
            <p style={{color: "var(--text-secondary)", fontSize: 16.5, maxWidth: 580, margin: "0 0 40px", textWrap: "pretty"}}>
                Mostly thinking out loud about the craft of making software — sometimes longer pieces, sometimes just notes I didn't want to lose.
            </p>

            <div className="writing-list">
                {DATA.posts.map(p => (
                    <a key={p.id} href={"#/writing/" + p.id} className="writing-row reveal">
                        <div className="writing-row__date">{p.dateDisplay}</div>
                        <div>
                            <h3 className="writing-row__title">{p.title}</h3>
                            <p className="writing-row__excerpt">{p.excerpt}</p>
                        </div>
                        <div className="writing-row__meta">{p.tag} · {p.readTime}</div>
                    </a>
                ))}
            </div>
        </div>
    );
}

function PostPage({ id }) {
    const post = DATA.posts.find(p => p.id === id);
    if (!post) {
        return (
            <div className="page">
                <a href="#/writing" className="post__back"><IconBack/> All writing</a>
                <h1 className="section__title">Post not found</h1>
                <p className="secondary">Maybe it moved. Try the <a href="#/writing" style={{color: "var(--accent)"}}>writing index</a>.</p>
            </div>
        );
    }
    return (
        <article className="post">
            <a href="#/writing" className="post__back"><IconBack/> All writing</a>
            <div className="post__meta">
                <span>{post.dateDisplay}</span>
                <span className="sep">·</span>
                <span>{post.tag}</span>
                <span className="sep">·</span>
                <span>{post.readTime} read</span>
            </div>
            <h1 className="post__title">{post.title}</h1>
            <p className="post__lede">{post.lede}</p>
            <div className="post__body" dangerouslySetInnerHTML={{ __html: post.body }}/>

            <hr style={{border: 0, borderTop: "1px solid var(--border)", margin: "64px 0 32px"}}/>
            <a href="#/writing" className="post__back" style={{marginBottom: 0}}><IconBack/> More writing</a>
        </article>
    );
}

function AboutPage() {
    const a = DATA.author;
    return (
        <div className="page">
            <div className="section__eyebrow">About</div>
            <div className="about-grid" style={{marginTop: 24}}>
                <aside className="about-side">
                    <div className="about-avatar">
                        {a.name.split(" ").map(n => n[0]).join("")}
                    </div>
                    <dl className="about-meta">
                        <dt>Based in</dt>
                        <dd>{a.location}</dd>
                        <dt>Discord</dt>
                        <dd><a href={"https://discord.com/users/" + a.discord} target="_blank" rel="noreferrer">@{a.discord}</a></dd>
                        <dt>Elsewhere</dt>
                        <dd><a href={"https://" + a.links.github} target="_blank" rel="noreferrer">{a.links.github}</a></dd>
                        <dd><a href={"https://" + a.links.twitter} target="_blank" rel="noreferrer">{a.links.twitter}</a></dd>
                    </dl>
                </aside>
                <div className="about-main">
                    <p className="lede">
                        I make web apps and games using high-level AI tools — shipping small, useful things from Perth.
                    </p>
                    <p>
                        I'm a software engineer based in Perth, Western Australia. My work sits at the intersection of web development and AI-assisted building — I use high-level AI tools as part of how I design, prototype, and ship. The output is mostly web apps and games.
                    </p>
                    <p>
                        Outside of building, I occasionally write down what I'm up to — research notes, project postmortems, and the occasional essay. Nothing too frequent, just when I have something worth saying.
                    </p>

                    <h3>What I'm up to now</h3>
                    <p>
                        Currently shipping a few web apps and tinkering on a browser-based game. You can see the latest on the <a href="#/projects" style={{color: "var(--accent)", borderBottom: "1px solid var(--accent-muted)"}}>projects page</a>.
                    </p>

                    <h3>Get in touch</h3>
                    <p>
                        If you want to talk — about a project, something I've written, or something you're working on — Discord is the best way. I read everything. I reply to most things.
                    </p>
                </div>
            </div>
        </div>
    );
}

/* ── Ambient background ─────────────────────────────────────────────────────── */
function Ambient() {
    return (
        <div className="ambient" aria-hidden="true">
            <div className="ambient__mesh ambient__mesh--a"/>
            <div className="ambient__mesh ambient__mesh--b"/>
            <div className="ambient__mesh ambient__mesh--c"/>
            <div className="ambient__noise"/>
        </div>
    );
}

/* ── Scroll reveal ─────────────────────────────────────────────────────────── */
function useScrollReveal(route) {
    useEffect(() => {
        const els = document.querySelectorAll(".reveal:not(.is-in)");
        if (!("IntersectionObserver" in window)) {
            els.forEach(el => el.classList.add("is-in"));
            return;
        }
        const io = new IntersectionObserver((entries) => {
            entries.forEach(en => {
                if (en.isIntersecting) {
                    en.target.classList.add("is-in");
                    io.unobserve(en.target);
                }
            });
        }, { rootMargin: "-5% 0px -5% 0px", threshold: 0.05 });
        els.forEach((el, i) => {
            el.style.transitionDelay = Math.min(i * 40, 240) + "ms";
            io.observe(el);
        });
        return () => io.disconnect();
    }, [route]);
}

/* ── App root ──────────────────────────────────────────────────────────────── */
function App() {
    const [theme, toggleTheme] = useTheme();
    const [route] = useRoute();
    useScrollReveal(route);

    let page;
    if (route === "/" || route === "") {
        page = <Home/>;
    } else if (route === "/projects") {
        page = <ProjectsPage/>;
    } else if (route === "/writing") {
        page = <WritingPage/>;
    } else if (route.startsWith("/writing/")) {
        page = <PostPage id={route.slice("/writing/".length)}/>;
    } else if (route === "/about") {
        page = <AboutPage/>;
    } else {
        page = (
            <div className="page">
                <h1 className="section__title">404</h1>
                <p className="secondary">That route doesn't exist. <a href="#/" style={{color: "var(--accent)"}}>Back home</a>.</p>
            </div>
        );
    }

    return (
        <div className="shell">
            <Ambient/>
            <Topbar route={route} theme={theme} onToggleTheme={toggleTheme}/>
            <main key={route}>{page}</main>
            <Footer/>
        </div>
    );
}

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