<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Community Intake</title>
    <style>
      :root {
        color-scheme: light;
        --bg: #f6f6f2;
        --panel: #ffffff;
        --soft: #ecebe3;
        --text: #161616;
        --muted: #66655f;
        --line: #d8d6cc;
        --accent: #8b6230;
        --error: #9b342f;
      }

      * { box-sizing: border-box; }
      [hidden] { display: none !important; }

      body {
        margin: 0;
        min-height: 100vh;
        background: var(--bg);
        color: var(--text);
        font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
        font-size: 15px;
        line-height: 1.5;
      }

      button,
      input,
      textarea {
        font: inherit;
      }

      button {
        cursor: pointer;
      }

      .app {
        width: min(1280px, calc(100% - 36px));
        margin: 0 auto;
        padding: 24px 0 42px;
      }

      .topbar {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 18px;
        padding-bottom: 16px;
        border-bottom: 1px solid var(--line);
      }

      .brand {
        display: grid;
        gap: 2px;
      }

      .brand strong {
        font-size: 18px;
      }

      .meta,
      .hint,
      .brand span {
        color: var(--muted);
      }

      .actions,
      .row {
        display: flex;
        gap: 10px;
        align-items: center;
        flex-wrap: wrap;
      }

      .btn {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-height: 40px;
        padding: 0 15px;
        border: 1px solid var(--text);
        border-radius: 6px;
        background: var(--text);
        color: white;
        font-weight: 700;
      }

      .btn:hover {
        border-color: var(--accent);
        background: var(--accent);
      }

      .btn.secondary {
        background: transparent;
        color: var(--text);
        border-color: var(--line);
      }

      .btn.secondary:hover {
        color: var(--accent);
        background: #fbfaf7;
      }

      .btn:disabled {
        opacity: 0.55;
        cursor: wait;
      }

      .view {
        margin-top: 30px;
      }

      .login {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(320px, 430px);
        gap: 64px;
        align-items: start;
      }

      h1,
      h2,
      h3,
      p {
        margin-top: 0;
      }

      h1 {
        max-width: 680px;
        margin-bottom: 14px;
        font-size: 64px;
        line-height: 0.96;
        letter-spacing: 0;
      }

      h2 {
        margin-bottom: 12px;
        font-size: 21px;
        letter-spacing: 0;
      }

      h3 {
        margin-bottom: 8px;
        font-size: 15px;
      }

      .lead {
        max-width: 570px;
        color: var(--muted);
        font-size: 18px;
      }

      .panel {
        border: 1px solid var(--line);
        border-radius: 8px;
        background: var(--panel);
        padding: 20px;
      }

      .form-grid {
        display: grid;
        gap: 14px;
      }

      .field {
        display: grid;
        gap: 7px;
      }

      label,
      .label {
        color: var(--muted);
        font-size: 12px;
        font-weight: 750;
        letter-spacing: 0;
        text-transform: uppercase;
      }

      input,
      textarea {
        width: 100%;
        border: 1px solid var(--line);
        border-radius: 6px;
        background: white;
        color: var(--text);
        padding: 11px 12px;
      }

      textarea {
        min-height: 120px;
        resize: vertical;
      }

      input:focus,
      textarea:focus {
        outline: 2px solid rgba(139, 98, 48, 0.18);
        border-color: var(--accent);
      }

      .workspace {
        display: grid;
        grid-template-columns: minmax(360px, 1fr) minmax(420px, 0.9fr);
        gap: 24px;
        align-items: start;
      }

      .stack {
        display: grid;
        gap: 16px;
      }

      .chat {
        min-height: 560px;
        display: grid;
        grid-template-rows: auto minmax(280px, 1fr) auto;
        gap: 14px;
      }

      .messages {
        min-height: 320px;
        max-height: 58vh;
        overflow-y: auto;
        padding-right: 4px;
        display: flex;
        flex-direction: column;
        gap: 10px;
      }

      .message {
        max-width: 86%;
        padding: 12px 14px;
        border-radius: 8px;
        white-space: pre-wrap;
      }

      .message.assistant {
        align-self: flex-start;
        background: var(--soft);
      }

      .message.user {
        align-self: flex-end;
        background: var(--text);
        color: white;
      }

      .composer {
        display: grid;
        gap: 10px;
      }

      .composer textarea {
        min-height: 92px;
      }

      .profile-editor textarea {
        min-height: 560px;
        font-family: "JetBrains Mono", Consolas, monospace;
        font-size: 13px;
        line-height: 1.55;
      }

      .profile-details {
        margin-top: 4px;
        padding-top: 12px;
        border-top: 1px solid var(--line);
      }

      .profile-details summary {
        cursor: pointer;
        color: var(--accent);
        font-weight: 800;
      }

      .profile-details form {
        margin-top: 14px;
      }

      .split {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 16px;
      }

      .list {
        list-style: none;
        margin: 12px 0 0;
        padding: 0;
        border-top: 1px solid var(--line);
      }

      .list li {
        display: flex;
        justify-content: space-between;
        gap: 12px;
        padding: 11px 0;
        border-bottom: 1px solid var(--line);
      }

      .list a,
      .file-name {
        color: var(--text);
        overflow-wrap: anywhere;
      }

      .row-action {
        border: 0;
        background: transparent;
        color: var(--accent);
        font-weight: 700;
      }

      .upload input {
        display: none;
      }

      .upload {
        justify-content: space-between;
        border: 1px dashed var(--line);
        border-radius: 6px;
        padding: 14px;
        background: #fbfaf7;
      }

      .upload-label {
        min-width: 0;
        color: var(--muted);
        overflow-wrap: anywhere;
        cursor: pointer;
      }

      .toast {
        position: fixed;
        right: 24px;
        bottom: 24px;
        max-width: 340px;
        padding: 13px 16px;
        border-radius: 8px;
        background: var(--text);
        color: white;
        box-shadow: 0 18px 60px rgba(0, 0, 0, 0.18);
        opacity: 0;
        transform: translateY(8px);
        transition: opacity 0.2s ease, transform 0.2s ease;
        pointer-events: none;
      }

      .toast.is-visible {
        opacity: 1;
        transform: translateY(0);
      }

      .toast.is-error {
        background: var(--error);
      }

      @media (max-width: 980px) {
        .login,
        .workspace,
        .split {
          grid-template-columns: 1fr;
        }

        .profile-editor textarea {
          min-height: 420px;
        }
      }

      @media (max-width: 620px) {
        .app {
          width: calc(100% - 28px);
          padding-top: 18px;
        }

        .topbar,
        .actions,
        .row {
          align-items: stretch;
          flex-direction: column;
        }

        .topbar {
          align-items: flex-start;
        }

        .actions,
        .btn {
          width: 100%;
        }

        h1 {
          font-size: 42px;
        }

        .message {
          max-width: 100%;
        }

      }
    </style>
  </head>
  <body>
    <div class="app">
      <header class="topbar">
        <div class="brand">
          <strong>Community Intake</strong>
          <span id="memberLabel">AI-guided profile builder</span>
        </div>
        <div class="actions">
          <button id="topSaveButton" class="btn" type="button" hidden>Save profile</button>
          <button id="logoutButton" class="btn secondary" type="button" hidden>Sign out</button>
        </div>
      </header>

      <section id="loginView" class="view login">
        <div>
          <h1>Tell the community who you are.</h1>
          <p class="lead">A private interviewer helps turn your projects, interests, expertise, stories, and boundaries into a useful member profile.</p>
        </div>

        <form id="loginForm" class="panel form-grid">
          <div class="field">
            <label for="communityInput">Community</label>
            <input id="communityInput" name="community_id" autocomplete="organization" required>
          </div>
          <div class="field">
            <label for="memberInput">Member</label>
            <input id="memberInput" name="member_id" autocomplete="username" required>
          </div>
          <div class="field">
            <label for="passwordInput">Password</label>
            <input id="passwordInput" name="password" type="password" autocomplete="current-password" required>
          </div>
          <button class="btn" type="submit">Sign in</button>
        </form>
      </section>

      <section id="passwordView" class="view" hidden>
        <form id="passwordForm" class="panel form-grid" style="max-width: 460px">
          <h2>Set your private password.</h2>
          <div class="field">
            <label for="currentPasswordInput">Current password</label>
            <input id="currentPasswordInput" name="current_password" type="password" autocomplete="current-password" required>
          </div>
          <div class="field">
            <label for="newPasswordInput">New password</label>
            <input id="newPasswordInput" name="new_password" type="password" autocomplete="new-password" required>
          </div>
          <button class="btn" type="submit">Continue</button>
        </form>
      </section>

      <section id="workspaceView" class="view workspace" hidden>
        <main class="stack">
          <section class="panel chat">
            <div>
              <h2>Interview</h2>
            </div>

            <div id="messages" class="messages"></div>

            <form id="chatForm" class="composer">
              <textarea id="chatInput" placeholder="Answer like you would in conversation. A few honest sentences are enough." required></textarea>
              <div class="row">
                <button class="btn" type="submit">Send</button>
                <button id="generateButton" class="btn secondary" type="button">Update draft from conversation</button>
                <button id="clearChatButton" class="btn secondary" type="button">Clear chat</button>
              </div>
            </form>
          </section>

          <div class="split">
            <section class="panel">
              <h2>References <span class="hint">(optional)</span></h2>
              <div class="row">
                <input id="linkInput" type="text" inputmode="url" autocapitalize="none" autocorrect="off" spellcheck="false" placeholder="https://">
                <button id="addLinkButton" class="btn secondary" type="button">Add</button>
              </div>
              <ul id="linksList" class="list"></ul>
            </section>

            <section class="panel">
              <h2>Files <span class="hint">(optional)</span></h2>
              <form id="uploadForm" class="upload row">
                <label id="uploadLabel" class="upload-label" for="fileInput">Choose a file</label>
                <input id="fileInput" type="file" required>
                <button class="btn secondary" type="submit">Upload</button>
              </form>
              <p class="hint" style="margin: 10px 0 0">PDF, DOCX, PPTX, CSV, TSV, MD, TXT.</p>
              <ul id="fileList" class="list"></ul>
            </section>
          </div>
        </main>

        <aside class="stack">
          <section class="panel profile-editor">
            <h2>Profile draft</h2>
            <details id="profileDetails" class="profile-details">
              <summary>Edit profile markdown</summary>
              <form id="profileForm" class="form-grid">
                <textarea id="profileText" spellcheck="false"></textarea>
                <button class="btn" type="submit">Save profile</button>
              </form>
            </details>
          </section>

          <section class="panel">
            <h2>Account</h2>
            <button id="accountToggleButton" class="btn secondary" type="button">Change password</button>
            <form id="accountPasswordForm" class="form-grid" hidden style="margin-top: 14px">
              <div class="field">
                <label for="accountCurrentPasswordInput">Current password</label>
                <input id="accountCurrentPasswordInput" name="current_password" type="password" autocomplete="current-password" required>
              </div>
              <div class="field">
                <label for="accountNewPasswordInput">New password</label>
                <input id="accountNewPasswordInput" name="new_password" type="password" autocomplete="new-password" required>
              </div>
              <button class="btn" type="submit">Save password</button>
            </form>
          </section>
        </aside>
      </section>
    </div>

    <div id="status" class="toast"></div>

    <script>
      const tokenKey = "communityIntakeToken";
      const backend =
        location.protocol === "file:" ||
        (["localhost", "127.0.0.1", "0.0.0.0"].includes(location.hostname) && location.port !== "5000")
          ? "http://127.0.0.1:5000"
          : "";

      const loginView = document.querySelector("#loginView");
      const passwordView = document.querySelector("#passwordView");
      const workspaceView = document.querySelector("#workspaceView");
      const loginForm = document.querySelector("#loginForm");
      const passwordForm = document.querySelector("#passwordForm");
      const accountPasswordForm = document.querySelector("#accountPasswordForm");
      const accountToggleButton = document.querySelector("#accountToggleButton");
      const profileDetails = document.querySelector("#profileDetails");
      const profileForm = document.querySelector("#profileForm");
      const profileText = document.querySelector("#profileText");
      const chatForm = document.querySelector("#chatForm");
      const chatInput = document.querySelector("#chatInput");
      const messagesEl = document.querySelector("#messages");
      const generateButton = document.querySelector("#generateButton");
      const clearChatButton = document.querySelector("#clearChatButton");
      const linkInput = document.querySelector("#linkInput");
      const addLinkButton = document.querySelector("#addLinkButton");
      const linksList = document.querySelector("#linksList");
      const uploadForm = document.querySelector("#uploadForm");
      const uploadLabel = document.querySelector("#uploadLabel");
      const fileInput = document.querySelector("#fileInput");
      const fileList = document.querySelector("#fileList");
      const topSaveButton = document.querySelector("#topSaveButton");
      const logoutButton = document.querySelector("#logoutButton");
      const memberLabel = document.querySelector("#memberLabel");
      const status = document.querySelector("#status");

      let token = localStorage.getItem(tokenKey) || "";
      let messages = [];
      let links = [];
      let draftedUserMessageCount = 0;
      let statusTimer = 0;

      loginForm.addEventListener("submit", async (event) => {
        event.preventDefault();
        const body = Object.fromEntries(new FormData(loginForm));
        setBusy(loginForm, true);
        try {
          const result = await api("/api/login", { method: "POST", body });
          token = result.token;
          localStorage.setItem(tokenKey, token);
          loginForm.reset();
          await showUser(result.user);
        } catch (error) {
          setStatus(error.message, true);
        } finally {
          setBusy(loginForm, false);
        }
      });

      passwordForm.addEventListener("submit", (event) => changePassword(event, passwordForm));
      accountPasswordForm.addEventListener("submit", (event) => changePassword(event, accountPasswordForm));

      accountToggleButton.addEventListener("click", () => {
        accountPasswordForm.hidden = !accountPasswordForm.hidden;
        accountToggleButton.textContent = accountPasswordForm.hidden ? "Change password" : "Cancel";
      });

      chatForm.addEventListener("submit", async (event) => {
        event.preventDefault();
        const content = chatInput.value.trim();
        if (!content) return;
        chatInput.value = "";
        messages.push({ role: "user", content });
        renderMessages();
        setBusy(chatForm, true);
        try {
          const result = await api("/api/intake/chat", {
            method: "POST",
            body: { messages, profile: profileText.value },
          });
          messages.push({ role: "assistant", content: result.message || "" });
          renderMessages();
        } catch (error) {
          setStatus(error.message, true);
        } finally {
          setBusy(chatForm, false);
          chatInput.focus();
        }
      });

      generateButton.addEventListener("click", async () => {
        generateButton.disabled = true;
        try {
          await updateDraftFromConversation();
          setStatus("Draft ready. Review and save it.");
        } catch (error) {
          setStatus(error.message, true);
        } finally {
          generateButton.disabled = false;
        }
      });

      clearChatButton.addEventListener("click", async () => {
        await startConversation();
        setStatus("Fresh conversation started.");
      });

      profileForm.addEventListener("submit", async (event) => {
        event.preventDefault();
        await saveProfile(true);
      });
      topSaveButton.addEventListener("click", () => saveProfile(true));

      logoutButton.addEventListener("click", async () => {
        if (!workspaceView.hidden && !(await saveProfile(true))) return;
        token = "";
        localStorage.removeItem(tokenKey);
        showLogin();
      });

      addLinkButton.addEventListener("click", addLink);
      linkInput.addEventListener("keydown", (event) => {
        if (event.key === "Enter") {
          event.preventDefault();
          addLink();
        }
      });

      fileInput.addEventListener("change", () => {
        uploadLabel.textContent = fileInput.files[0]?.name || "Choose a file";
      });

      uploadForm.addEventListener("submit", async (event) => {
        event.preventDefault();
        const file = fileInput.files[0];
        if (!file) return;
        setBusy(uploadForm, true);
        try {
          const result = await api("/api/files", {
            method: "POST",
            body: { file_name: file.name, content_base64: await readBase64(file) },
          });
          fileInput.value = "";
          uploadLabel.textContent = "Choose a file";
          renderFiles(result.files || []);
          setStatus("File uploaded.");
        } catch (error) {
          setStatus(error.message, true);
        } finally {
          setBusy(uploadForm, false);
        }
      });

      async function boot() {
        if (!token) {
          showLogin();
          return;
        }
        try {
          await showUser(await api("/api/me"));
        } catch {
          token = "";
          localStorage.removeItem(tokenKey);
          showLogin();
        }
      }

      async function showUser(user) {
        memberLabel.textContent = `${user.community_id} / ${user.member_id}`;
        topSaveButton.hidden = true;
        logoutButton.hidden = false;
        loginView.hidden = true;
        passwordView.hidden = !user.password_change_required;
        workspaceView.hidden = Boolean(user.password_change_required);
        if (user.password_change_required) {
          setStatus("Please set a private password.");
          return;
        }
        topSaveButton.hidden = false;
        await Promise.all([loadProfile(), loadFiles()]);
        await startConversation();
      }

      function showLogin() {
        loginView.hidden = false;
        passwordView.hidden = true;
        workspaceView.hidden = true;
        topSaveButton.hidden = true;
        logoutButton.hidden = true;
        memberLabel.textContent = "AI-guided profile builder";
        profileText.value = "";
        profileDetails.open = false;
        messages = [];
        links = [];
        draftedUserMessageCount = 0;
        renderMessages();
        renderLinks();
        renderFiles([]);
        setStatus("");
      }

      async function changePassword(event, form) {
        event.preventDefault();
        const body = Object.fromEntries(new FormData(form));
        setBusy(form, true);
        try {
          const result = await api("/api/password", { method: "POST", body });
          token = result.token;
          localStorage.setItem(tokenKey, token);
          form.reset();
          accountPasswordForm.hidden = true;
          accountToggleButton.textContent = "Change password";
          await showUser(result.user);
          setStatus("Password saved.");
        } catch (error) {
          setStatus(error.message, true);
        } finally {
          setBusy(form, false);
        }
      }

      async function loadProfile() {
        const result = await api("/api/profile");
        profileText.value = result.profile || "";
        links = Array.isArray(result.links) ? result.links : [];
        renderLinks();
      }

      async function loadFiles() {
        const result = await api("/api/files");
        renderFiles(result.files || []);
      }

      async function startConversation() {
        messages = [];
        draftedUserMessageCount = 0;
        renderMessages();
        setBusy(chatForm, true);
        try {
          const result = await api("/api/intake/chat", {
            method: "POST",
            body: { messages, profile: profileText.value },
          });
          messages = [{ role: "assistant", content: result.message || "" }];
          renderMessages();
        } catch (error) {
          messages = [{
            role: "assistant",
            content: "What is something currently taking up more of your attention than people around you might realize?",
          }];
          renderMessages();
          setStatus(error.message, true);
        } finally {
          setBusy(chatForm, false);
          chatInput.focus();
        }
      }

      async function updateDraftFromConversation() {
        const result = await api("/api/intake/profile", {
          method: "POST",
          body: { messages, profile: profileText.value },
        });
        profileText.value = result.profile || profileText.value;
        profileDetails.open = true;
        draftedUserMessageCount = userMessageCount();
      }

      async function saveProfile(offerConversationUpdate = false) {
        setBusy(profileForm, true);
        topSaveButton.disabled = true;
        try {
          if (offerConversationUpdate && conversationNeedsDraft()) {
            const useConversation = window.confirm(
              "Use this conversation to update the profile draft before saving?\n\nOK: update the draft from chat, then save.\nCancel: save the current profile text only."
            );
            if (useConversation) await updateDraftFromConversation();
          }
          await api("/api/profile", {
            method: "POST",
            body: { profile: profileText.value, links },
          });
          setStatus("Profile saved.");
          return true;
        } catch (error) {
          setStatus(error.message, true);
          return false;
        } finally {
          setBusy(profileForm, false);
          topSaveButton.disabled = false;
        }
      }

      async function addLink() {
        let value = linkInput.value.trim();
        if (!value) return;
        if (!/^[a-z][a-z0-9+.-]*:/i.test(value)) value = `https://${value}`;
        links.push(value);
        linkInput.value = "";
        renderLinks();
        await saveProfile();
      }

      async function deleteFile(path) {
        try {
          const result = await api(`/api/files?path=${encodeURIComponent(path)}`, { method: "DELETE" });
          renderFiles(result.files || []);
          setStatus("File removed.");
        } catch (error) {
          setStatus(error.message, true);
        }
      }

      async function api(path, options = {}) {
        const headers = { "Content-Type": "application/json" };
        if (token) headers.Authorization = `Bearer ${token}`;
        let response;
        try {
          response = await fetch(`${backend}${path}`, {
            method: options.method || "GET",
            headers,
            body: options.body ? JSON.stringify(options.body) : undefined,
          });
        } catch {
          throw new Error("Backend did not answer. Start the intake app.");
        }
        const data = await response.json();
        if (!response.ok) throw new Error(data.error || "Request failed.");
        return data;
      }

      function renderMessages() {
        messagesEl.replaceChildren();
        if (!messages.length) {
          const intro = document.createElement("div");
          intro.className = "message assistant";
          intro.textContent = "Starting a fresh conversation.";
          messagesEl.append(intro);
          return;
        }
        for (const message of messages) {
          const item = document.createElement("div");
          item.className = `message ${message.role}`;
          item.textContent = message.content;
          messagesEl.append(item);
        }
        messagesEl.scrollTop = messagesEl.scrollHeight;
      }

      function renderLinks() {
        linksList.replaceChildren();
        if (!links.length) {
          linksList.append(emptyItem("No references yet."));
          return;
        }
        links.forEach((link, index) => {
          const item = document.createElement("li");
          const anchor = document.createElement("a");
          anchor.href = link;
          anchor.textContent = link;
          anchor.target = "_blank";
          anchor.rel = "noopener noreferrer";
          const remove = document.createElement("button");
          remove.type = "button";
          remove.className = "row-action";
          remove.textContent = "Remove";
          remove.addEventListener("click", async () => {
            links.splice(index, 1);
            renderLinks();
            await saveProfile();
          });
          item.append(anchor, remove);
          linksList.append(item);
        });
      }

      function renderFiles(files) {
        fileList.replaceChildren();
        if (!files.length) {
          fileList.append(emptyItem("No files yet."));
          return;
        }
        for (const file of files) {
          const item = document.createElement("li");
          const info = document.createElement("div");
          info.innerHTML = `<div class="file-name"></div><div class="meta">${formatDate(file.modified_at)}</div>`;
          info.querySelector(".file-name").textContent = file.name;
          const remove = document.createElement("button");
          remove.type = "button";
          remove.className = "row-action";
          remove.textContent = "Remove";
          remove.addEventListener("click", () => deleteFile(file.relative_path));
          item.append(info, remove);
          fileList.append(item);
        }
      }

      function userMessageCount() {
        return messages.filter((message) => message.role === "user").length;
      }

      function conversationNeedsDraft() {
        return userMessageCount() > draftedUserMessageCount;
      }

      function emptyItem(text) {
        const item = document.createElement("li");
        item.className = "meta";
        item.textContent = text;
        return item;
      }

      function setBusy(form, busy) {
        for (const element of form.querySelectorAll("button, input, textarea")) {
          element.disabled = busy;
        }
      }

      function setStatus(message, isError = false) {
        status.textContent = message;
        status.classList.toggle("is-error", Boolean(isError));
        clearTimeout(statusTimer);
        if (!message) {
          status.classList.remove("is-visible");
          return;
        }
        status.classList.add("is-visible");
        statusTimer = window.setTimeout(() => status.classList.remove("is-visible"), 3200);
      }

      function readBase64(file) {
        return new Promise((resolve, reject) => {
          const reader = new FileReader();
          reader.addEventListener("load", () => resolve(String(reader.result).split(",", 2)[1]));
          reader.addEventListener("error", () => reject(reader.error));
          reader.readAsDataURL(file);
        });
      }

      function formatDate(value) {
        const date = new Date(value);
        return Number.isNaN(date.getTime()) ? "" : date.toLocaleString();
      }

      boot();
    </script>
  </body>
</html>
