// note.jsx — Dan Barrett, individual writing/blog post page.

const { useEffect, useMemo, useState } = React;

function TopBar() {
  return (
    <header className="topbar">
      <a href="index.html" className="topbar__mark">danbarrettofficial.com</a>
      <ul className="topbar__nav">
        <li><a href="index.html#now">Now</a></li>
        <li><a href="work.html">Work</a></li>
        <li><a href="index.html#shows">Shows</a></li>
        <li><a href="writing.html" className="is-current">Writing</a></li>
      </ul>
    </header>
  );
}

function getPosts() {
  return window.NOTES_DATA?.posts || [];
}

function getSlugFromHash() {
  return decodeURIComponent(window.location.hash.replace(/^#/, ""));
}

function escapeHtml(text) {
  return text
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;");
}

function renderInline(text) {
  const links = [];
  const tokenizedText = text.replace(
    /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,
    (_, label, url) => {
      const token = `%%MARKDOWN_LINK_${links.length}%%`;
      links.push(
        `<a href="${escapeHtml(url)}" target="_blank" rel="noreferrer">${escapeHtml(label)}</a>`
      );
      return token;
    }
  );

  let html = escapeHtml(tokenizedText);

  html = html.replace(
    /https?:\/\/[^\s<]+/g,
    (url) => `<a href="${url}" target="_blank" rel="noreferrer">${url}</a>`
  );
  html = html.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>");
  html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
  html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
  html = html.replace(
    /%%MARKDOWN_LINK_(\d+)%%/g,
    (_, index) => links[Number(index)]
  );

  return html;
}

function normalizeMarkdown(markdown) {
  return markdown
    .replace(
      "https://en.wikipedia.org/wiki/Giles_Corey_(band)?ref=betterquestions.co",
      "https://en.wikipedia.org/wiki/Giles_Corey_%28band%29?ref=betterquestions.co"
    )
    .replace(
      /^\*\*Better Questions is supported by readers like you\.[\s\S]*?Thanks\.\*\*\s*/m,
      ""
    )
    .replace(/^\*\*----\*\*\s*/m, "")
    .replace(/^---\s*/m, "");
}

function stripFrontmatter(text) {
  const lines = text.split("\n");
  if (lines[0]?.trim() !== "---") {
    return text;
  }

  for (let i = 1; i < lines.length; i += 1) {
    if (lines[i].trim() === "---") {
      return lines.slice(i + 1).join("\n");
    }
  }

  return text;
}

function parseMarkdown(markdown) {
  const lines = normalizeMarkdown(markdown).split("\n");
  const blocks = [];
  let i = 0;
  let firstParagraph = true;

  const flushParagraph = (buffer) => {
    const text = buffer.map((line) => line.trim()).join(" ").trim();
    if (!text) return;
    blocks.push({
      type: "paragraph",
      className: firstParagraph ? "post__dropcap" : "",
      html: renderInline(text),
    });
    firstParagraph = false;
  };

  while (i < lines.length) {
    const line = lines[i];
    const trimmed = line.trim();

    if (!trimmed) {
      i += 1;
      continue;
    }

    if (trimmed === "<!-- EUROPE_TOUR_DATES -->") {
      blocks.push({ type: "europe-tour" });
      i += 1;
      continue;
    }

    if (trimmed === "<!-- US_TOUR_DATES -->") {
      blocks.push({ type: "us-tour" });
      i += 1;
      continue;
    }

    if (/^[-*]{3,}$/.test(trimmed)) {
      i += 1;
      continue;
    }

    if (/^\d+:\s+/.test(trimmed)) {
      blocks.push({
        type: "heading",
        html: renderInline(trimmed.replace(/^(\d+):\s+/, "$1. ")),
      });
      i += 1;
      continue;
    }

    if (/^>\s*/.test(trimmed)) {
      const quoteLines = [];
      while (i < lines.length && /^>\s*/.test(lines[i].trim())) {
        quoteLines.push(lines[i].trim().replace(/^>\s*/, ""));
        i += 1;
      }
      blocks.push({
        type: "quote",
        html: quoteLines
          .filter(Boolean)
          .map((quoteLine) => `<p>${renderInline(quoteLine)}</p>`)
          .join(""),
      });
      continue;
    }

    if (/^- /.test(trimmed)) {
      const items = [];
      while (i < lines.length && /^- /.test(lines[i].trim())) {
        items.push(renderInline(lines[i].trim().replace(/^- /, "")));
        i += 1;
      }
      blocks.push({ type: "list", items });
      continue;
    }

    const paragraph = [];
    while (i < lines.length) {
      const current = lines[i].trim();
      if (
        !current ||
        current === "<!-- EUROPE_TOUR_DATES -->" ||
        current === "<!-- US_TOUR_DATES -->" ||
        /^[-*]{3,}$/.test(current) ||
        /^\d+:\s+/.test(current) ||
        /^>\s*/.test(current) ||
        /^- /.test(current)
      ) {
        break;
      }
      paragraph.push(lines[i]);
      i += 1;
    }
    flushParagraph(paragraph);
  }

  return blocks;
}

function getSelectedPost(posts, slug) {
  return posts.find((post) => post.slug === slug) || posts[0] || null;
}

function PostHeader({ post }) {
  return (
    <header className="post__head">
      <a href="writing.html" className="post__back">← All writing</a>
      <div className="post__meta">
        <span>{post.date}</span>
        <span className="post__meta-sep">·</span>
        <span>{post.readtime} read</span>
        <span className="post__meta-sep">·</span>
        <span>{post.tag}</span>
      </div>
      <h1 className="post__title">{post.title}</h1>
    </header>
  );
}

function UsTourDates() {
  return (
    <section className="us-tour-dates" id="us-dates" aria-label="Giles Corey US tour dates">
      <h2 className="tour-dates-heading">United States · November 2026</h2>
      <p>
        And a reminder: I&apos;m also playing these East Coast shows in November with Kathryn Mohr and Val Acton Loper.
      </p>
      <ol className="tour-dates-list">
        {window.US_TOUR.shows.map((show) => (
          <li className="tour-date" key={show.date}>
            <time>{show.date}</time>
            <div className="tour-date__place">
              <strong>{show.city}</strong>
              <span>{show.venue}</span>
            </div>
            <a className="tour-date__ticket" href={show.tickets} target="_blank" rel="noreferrer"
               aria-label={`Tickets for Giles Corey in ${show.city} at ${show.venue}`}>
              Tickets ↗
            </a>
          </li>
        ))}
      </ol>
    </section>
  );
}

function PostBody({ post, blocks, loading, error }) {
  if (loading) {
    return (
      <article className="post">
        <p>Loading post…</p>
      </article>
    );
  }

  if (error) {
    return (
      <article className="post">
        <p>Couldn&apos;t load this post right now.</p>
      </article>
    );
  }

  return (
    <article className="post">
      {post.image && (
        <figure className="post__image-wrap">
          <img className="post__image" src={post.image} alt={post.imageAlt || ""} />
          {post.imageCredit && <figcaption className="post__image-credit">{post.imageCredit}</figcaption>}
        </figure>
      )}
      {post.originalUrl && (
        <p className="post__source">
          Republishing from the original BetterQuestions.co post. Read the original{" "}
          <a href={post.originalUrl} target="_blank" rel="noreferrer">here</a>.
        </p>
      )}
      {blocks.map((block, index) => {
        if (block.type === "europe-tour") {
          return <EuropeTourDates key={index} />;
        }

        if (block.type === "us-tour") {
          return <UsTourDates key={index} />;
        }

        if (block.type === "heading") {
          return (
            <h2
              key={index}
              className="post__h2"
              dangerouslySetInnerHTML={{ __html: block.html }}
            />
          );
        }

        if (block.type === "quote") {
          return (
            <blockquote
              key={index}
              className="post__quote"
              dangerouslySetInnerHTML={{ __html: block.html }}
            />
          );
        }

        if (block.type === "list") {
          return (
            <ul key={index} className="post__list">
              {block.items.map((item, itemIndex) => (
                <li key={itemIndex} dangerouslySetInnerHTML={{ __html: item }} />
              ))}
            </ul>
          );
        }

        return (
          <p
            key={index}
            className={block.className}
            dangerouslySetInnerHTML={{ __html: block.html }}
          />
        );
      })}
    </article>
  );
}

function PostFoot({ previousPost, nextPost }) {
  return (
    <footer className="post__foot">
      <div className="post__foot-nav">
        {previousPost ? (
          <a href={`note.html#${previousPost.slug}`} className="post__foot-link">
            <span className="post__foot-kicker">Previous</span>
            <span className="post__foot-title">{previousPost.title}</span>
          </a>
        ) : <span />}

        {nextPost ? (
          <a href={`note.html#${nextPost.slug}`} className="post__foot-link post__foot-link--next">
            <span className="post__foot-kicker">Next</span>
            <span className="post__foot-title">{nextPost.title}</span>
          </a>
        ) : <span />}
      </div>
      <div className="foot">
        <span>© Dan Barrett · 2005 — 2026</span>
        <ul className="foot__links">
          <li><a href="mailto:danielbarrett@gmail.com" className="ilink">Email</a></li>
          <li><span className="ilink">RSS</span></li>
        </ul>
      </div>
    </footer>
  );
}

/* ---------- App ---------- */
const NOTE_TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "ink",
  "dropcap": true,
  "columnWidth": 620
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(NOTE_TWEAK_DEFAULTS);
  const showTweaks = new URLSearchParams(window.location.search).has("tweaks");
  const posts = useMemo(() => getPosts(), []);
  const [slug, setSlug] = useState(getSlugFromHash());
  const [blocks, setBlocks] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);

  const post = getSelectedPost(posts, slug);
  const postIndex = post ? posts.findIndex((item) => item.slug === post.slug) : -1;
  const previousPost = postIndex >= 0 ? posts[postIndex + 1] || null : null;
  const nextPost = postIndex > 0 ? posts[postIndex - 1] || null : null;

  useEffect(() => {
    const onHashChange = () => setSlug(getSlugFromHash());
    window.addEventListener("hashchange", onHashChange);
    return () => window.removeEventListener("hashchange", onHashChange);
  }, []);

  useEffect(() => {
    document.documentElement.classList.toggle("theme-paper", t.theme === "paper");
    document.documentElement.style.setProperty("--col-w", `${t.columnWidth}px`);
    document.documentElement.classList.toggle("no-dropcap", !t.dropcap);
  }, [t.theme, t.columnWidth, t.dropcap]);

  useEffect(() => {
    if (!post) return;

    let isCancelled = false;
    setLoading(true);
    setError(false);

    fetch(post.archiveUrl)
      .then((response) => {
        if (!response.ok) throw new Error("Failed to fetch post");
        return response.text();
      })
      .then((text) => {
        if (isCancelled) return;
        const markdown = stripFrontmatter(text);
        setBlocks(parseMarkdown(markdown));
        setLoading(false);
        document.title = `${post.title} — The official website of musician Dan Barrett`;
      })
      .catch(() => {
        if (isCancelled) return;
        setError(true);
        setLoading(false);
      });

    return () => {
      isCancelled = true;
    };
  }, [post]);

  if (!post) {
    return null;
  }

  return (
    <React.Fragment>
      <div className="page">
        <TopBar />
        <PostHeader post={post} />
        <PostBody post={post} blocks={blocks} loading={loading} error={error} />
        <Subscribe />
        <PostFoot previousPost={previousPost} nextPost={nextPost} />
      </div>

      {showTweaks && <TweaksPanel title="Tweaks">
        <TweakSection label="Mood" />
        <TweakRadio  label="Theme"
                     value={t.theme}
                     options={["ink", "paper"]}
                     onChange={(v) => setTweak("theme", v)} />

        <TweakSection label="Typography" />
        <TweakToggle label="Drop cap"
                     value={t.dropcap}
                     onChange={(v) => setTweak("dropcap", v)} />

        <TweakSection label="Layout" />
        <TweakSlider label="Column width"
                     value={t.columnWidth}
                     min={520} max={760} step={10} unit="px"
                     onChange={(v) => setTweak("columnWidth", v)} />
      </TweaksPanel>}
    </React.Fragment>
  );
}

Object.assign(window, { TopBar, PostHeader, PostBody, PostFoot, App });
