const { useState, useEffect, useRef } = React;

const FONT_IMPORT = "https://fonts.googleapis.com/css2?family=Source+Serif+4:opsz,wght@8..60,400;8..60,600&family=Inter:wght@400;500&display=swap";

// Pure-JS SHA-256 — deliberately not using crypto.subtle, since that API
// only works in a "secure context" (HTTPS or localhost). This app needs to
// keep working over plain http://ip:port during setup, before HTTPS is
// configured, so the hash function can't depend on a secure context.
async function sha256(text) {
  function rightRotate(value, amount) {
    return (value >>> amount) | (value << (32 - amount));
  }
  const mathPow = Math.pow;
  const maxWord = mathPow(2, 32);
  let result = "";
  const words = [];
  const asciiBitLength = text.length * 8;

  let hash = (sha256.h = sha256.h || []);
  const k = (sha256.k = sha256.k || []);
  let primeCounter = k.length;
  const isComposite = {};
  for (let candidate = 2; primeCounter < 64; candidate++) {
    if (!isComposite[candidate]) {
      for (let i = 0; i < 313; i += candidate) isComposite[i] = candidate;
      hash[primeCounter] = (mathPow(candidate, 0.5) * maxWord) | 0;
      k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0;
    }
  }
  text = unescape(encodeURIComponent(text));
  words[asciiBitLength >> 5] |= 0x80 << (24 - (asciiBitLength % 32));
  words[(((asciiBitLength + 64) >> 9) << 4) + 15] = asciiBitLength;

  for (let j = 0; j < text.length; j++) {
    words[j >> 2] |= text.charCodeAt(j) << (24 - (j % 4) * 8);
  }

  const w = [];
  const hashCopy = hash.slice(0, 8);
  hash = hashCopy;

  for (let i = 0; i < words.length; i += 16) {
    const oldHash = hash.slice(0);
    for (let j = 0; j < 64; j++) {
      let w1;
      if (j < 16) {
        w1 = w[j] = words[j + i] | 0;
      } else {
        const w15 = w[j - 15];
        const w2 = w[j - 2];
        w1 = w[j] =
          ((rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) +
            w[j - 7] +
            (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10)) +
            w[j - 16]) |
          0;
      }
      const t1 =
        (hash[7] +
          (rightRotate(hash[4], 6) ^ rightRotate(hash[4], 11) ^ rightRotate(hash[4], 25)) +
          ((hash[4] & hash[5]) ^ (~hash[4] & hash[6])) +
          k[j] +
          w1) |
        0;
      const t2 =
        ((rightRotate(hash[0], 2) ^ rightRotate(hash[0], 13) ^ rightRotate(hash[0], 22)) +
          ((hash[0] & hash[1]) ^ (hash[0] & hash[2]) ^ (hash[1] & hash[2]))) |
        0;
      hash = [(t1 + t2) | 0].concat(hash.slice(0, 7));
      hash[4] = (hash[4] + t1) | 0;
    }
    for (let j = 0; j < 8; j++) {
      hash[j] = (hash[j] + oldHash[j]) | 0;
    }
  }

  for (let i = 0; i < 8; i++) {
    for (let j = 3; j + 1; j--) {
      const b = (hash[i] >> (j * 8)) & 255;
      result += (b < 16 ? "0" : "") + b.toString(16);
    }
  }
  return result;
}

let AUTH_HASH = null;

async function apiFetch(url, options = {}) {
  const headers = { ...(options.headers || {}), "X-CRM-Auth": AUTH_HASH || "" };
  const res = await fetch(url, { ...options, headers });
  return res;
}

const uid = () => Math.random().toString(36).slice(2, 10);

function daysSince(dateStr) {
  if (!dateStr) return null;
  const then = new Date(dateStr);
  const now = new Date();
  return Math.floor((now - then) / (1000 * 60 * 60 * 24));
}

function relTime(dateStr) {
  const d = daysSince(dateStr);
  if (d === null) return "Inget möte loggat";
  if (d === 0) return "Idag";
  if (d === 1) return "Igår";
  if (d < 7) return `${d} dagar sedan`;
  if (d < 31) return `${Math.floor(d / 7)} v sedan`;
  if (d < 365) return `${Math.floor(d / 30)} mån sedan`;
  return `${Math.floor(d / 365)} år sedan`;
}

function formatDate(dateStr) {
  if (!dateStr) return "";
  const d = new Date(dateStr);
  return d.toLocaleDateString("sv-SE", { year: "numeric", month: "short", day: "numeric" });
}

function latestMeeting(contact) {
  if (!contact.meetings || contact.meetings.length === 0) return null;
  return [...contact.meetings].sort((a, b) => new Date(b.date) - new Date(a.date))[0];
}

function daysUntilBirthday(birthday) {
  if (!birthday) return null;
  const now = new Date();
  const b = new Date(birthday);
  let next = new Date(now.getFullYear(), b.getMonth(), b.getDate());
  next.setHours(0, 0, 0, 0);
  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  if (next < today) next = new Date(now.getFullYear() + 1, b.getMonth(), b.getDate());
  return Math.round((next - today) / (1000 * 60 * 60 * 24));
}

function formatBirthday(birthday) {
  if (!birthday) return "";
  const b = new Date(birthday);
  return b.toLocaleDateString("sv-SE", { month: "long", day: "numeric" });
}

const TAG_OPTIONS = ["Familj & Vänner", "Bekant", "Prio Nätverk", "Övrigt Nätverk"];

const TAG_COLORS = {
  "Familj & Vänner": "#A14B2B",
  Bekant: "#8B6D2F",
  "Prio Nätverk": "#4B5D42",
  "Övrigt Nätverk": "#4B7A6B",
};

const emptyContact = () => ({
  id: uid(),
  name: "",
  role: "",
  company: "",
  previousCompanies: "",
  email: "",
  phone: "",
  family: "",
  interests: "",
  allergies: "",
  notes: "",
  tag: "",
  birthday: "",
  meetings: [],
});

const SEED_CONTACTS = [{"name": "Kristofer Runnquist", "role": "", "company": "", "previousCompanies": "", "email": "kr@runnquist.co", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Henrik Blomquist", "role": "", "company": "", "previousCompanies": "", "email": "henrik.blomquist@bure.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Mikael Norberg (Andrea)", "role": "", "company": "", "previousCompanies": "", "email": "mikael@cmovie.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Jan Swärd", "role": "", "company": "", "previousCompanies": "", "email": "jan.sward@bridgepoint.eu", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Peter Bergmark", "role": "", "company": "", "previousCompanies": "", "email": "peter.bergmark@gmail.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Mikael Freudmann", "role": "", "company": "", "previousCompanies": "", "email": "mikael@freudmann.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Johan Rydmark", "role": "", "company": "", "previousCompanies": "", "email": "johanrydmark@outlook.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Patrice Jabet", "role": "", "company": "", "previousCompanies": "", "email": "patrice.jabet@fsncapital.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Mårten Andersson", "role": "", "company": "", "previousCompanies": "", "email": "marten.andersson@volati.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Simon Angeldorff", "role": "", "company": "", "previousCompanies": "", "email": "simon.angeldorff@gmail.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Arash Pendari", "role": "", "company": "", "previousCompanies": "", "email": "arash@vionlabs.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Kristian Bengtsson", "role": "", "company": "", "previousCompanies": "", "email": "kristian.l.bengtsson@gmail.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Carl Bergsten", "role": "", "company": "", "previousCompanies": "", "email": "carl.engstroem@nordstjernan.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Jens Bergsten", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Jonas Bergstrand", "role": "", "company": "", "previousCompanies": "", "email": "jonas.bergstrand@accesspartners.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Jonas Bergström", "role": "", "company": "", "previousCompanies": "", "email": "Jonas.Bergstrom@vinge.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Andreas Bladh", "role": "", "company": "", "previousCompanies": "", "email": "bladhandreas@gmail.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Alexander Brunlid", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Jacob Brögger", "role": "", "company": "", "previousCompanies": "", "email": "jacob.brogger@neapartners.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Fredrik Börjesson", "role": "", "company": "", "previousCompanies": "", "email": "fredrik.borjesson@tisenhult.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Göran Carlson", "role": "", "company": "", "previousCompanies": "", "email": "goran@carlsonadvisor.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Victor Carlsson", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Christian Cederholm", "role": "", "company": "", "previousCompanies": "", "email": "Christian.Cederholm@investorab.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Thomas Centerlind", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Johan Conradsson", "role": "", "company": "", "previousCompanies": "", "email": "johan.conradsson@procuritas.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Lars Bärnheim", "role": "Senior Legal Consultant", "company": "", "previousCompanies": "", "email": "Lars.Barnheim@hannessnellman.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Alex Butler", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Andreas Kihlblom", "role": "", "company": "", "previousCompanies": "", "email": "akihlblom@gmail.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Martin Oliw", "role": "", "company": "", "previousCompanies": "", "email": "Martin.Oliw@ceviancapital.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Fredrik Milton", "role": "", "company": "", "previousCompanies": "", "email": "fredrik.milton@gmail.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Johan Ekberg", "role": "", "company": "", "previousCompanies": "", "email": "johan@tmg.ai", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Stefan Tengvall", "role": "", "company": "", "previousCompanies": "", "email": "stefan.tengvall@scrive.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Fredrik Norrman", "role": "", "company": "", "previousCompanies": "", "email": "fredrik.norrman@se.ey.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Magnus Omstedt", "role": "", "company": "", "previousCompanies": "", "email": "magnus.omstedt@gmail.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Matti Raasakka", "role": "", "company": "", "previousCompanies": "", "email": "matti.raasakka@gmail.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Christopher Magnani", "role": "", "company": "", "previousCompanies": "", "email": "christopher@themobilelife.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Carl Hökfelt", "role": "", "company": "", "previousCompanies": "", "email": "c_hokfelt@yahoo.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Peder Egnell", "role": "", "company": "", "previousCompanies": "", "email": "peder@egnell.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Axel Forssell", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Johan Lilja", "role": "", "company": "", "previousCompanies": "", "email": "JOHANLILJA@OUTLOOK.COM", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Carl-Fredrik Strid", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Mikael Norbäck", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Andreas Stenbäck", "role": "", "company": "", "previousCompanies": "", "email": "andreas.stenback@volati.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Niklas Sjögren", "role": "", "company": "", "previousCompanies": "", "email": "n.sjogren@intrum.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Sighsten Dahl", "role": "", "company": "", "previousCompanies": "", "email": "sighsten@gmail.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Henrik Saläng", "role": "", "company": "", "previousCompanies": "", "email": "henrik.salang@gmail.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Alf Metelius", "role": "", "company": "", "previousCompanies": "", "email": "alf.metelius@stellacapital.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Anders Steen", "role": "", "company": "", "previousCompanies": "", "email": "anders.steen@bragnuminvest.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Bengt Maunsbach", "role": "", "company": "", "previousCompanies": "", "email": "Bengt.Maunsbach@altor.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Bernt Ivarsson", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Björn Magnusson", "role": "", "company": "", "previousCompanies": "", "email": "Bjorn.Magnusson@hm.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Bob Johansson", "role": "", "company": "", "previousCompanies": "", "email": "bob.johanson@nybronadvokater.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Carl Ekerling", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Carl Grevelius", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Carl Lindskog", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Carl Reuterswärd", "role": "", "company": "", "previousCompanies": "", "email": "carlfredrik.reutersward@abgsc.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Carl Settergren", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Christer Eriksson", "role": "", "company": "", "previousCompanies": "", "email": "Christer.Eriksson@investorab.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Christian Drougge", "role": "", "company": "", "previousCompanies": "", "email": "christian.drougge@gmail.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Christian Magnuson", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Christopher Bley", "role": "", "company": "", "previousCompanies": "", "email": "christopher.bley@bridgepoint.eu", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Christopher Ekdahl", "role": "", "company": "", "previousCompanies": "", "email": "christopher.ekdahl@nordiccapital.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Daniel Hjortström", "role": "", "company": "", "previousCompanies": "", "email": "daniel.hjortstrom@besikta.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Övrigt Nätverk", "birthday": "", "meetings": []}, {"name": "Björn Nilsson", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Carl Westerberg", "role": "", "company": "", "previousCompanies": "", "email": "carl.westerberg@gda.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Conny Ternström", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Daniel Åhlin", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Daniel Lindley", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "David Sjöborg", "role": "Partner", "company": "", "previousCompanies": "", "email": "sjoborg@mojaveadvisory.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Sandra Haglind", "role": "", "company": "", "previousCompanies": "", "email": "sandra.haglind@ica.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Karl Sjöborg", "role": "", "company": "", "previousCompanies": "", "email": "karl.sjoborg@mathworks.com", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Fredrik Sjöborg", "role": "", "company": "", "previousCompanies": "", "email": "fredrik.sjoborg@seacastle.se", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}, {"name": "Marie Lindskog", "role": "", "company": "", "previousCompanies": "", "email": "", "phone": "", "family": "", "interests": "", "allergies": "", "notes": "", "tag": "Familj & Vänner", "birthday": "", "meetings": []}].map((c) => ({ ...emptyContact(), ...c, id: uid() }));

function PasswordGate({ onUnlock }) {
  const [pw, setPw] = useState("");
  const [error, setError] = useState("");
  const [checking, setChecking] = useState(false);

  async function submit() {
    setChecking(true);
    try {
      const hash = await sha256(pw);
      const res = await fetch("/api/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ hash }),
      });
      const data = await res.json();
      if (data.ok) {
        AUTH_HASH = hash;
        try {
          localStorage.setItem("crm_unlocked_hash", hash);
        } catch {}
        onUnlock();
      } else {
        setError("Fel lösenord.");
      }
    } catch {
      setError("Kunde inte nå servern. Försök igen.");
    }
    setChecking(false);
  }

  return (
    <div style={gateStyles.wrap}>
      <div style={gateStyles.box}>
        <p style={gateStyles.title}>Kontakter</p>
        <p style={gateStyles.subtitle}>Ange lösenord för att fortsätta</p>
        <input
          type="password"
          style={gateStyles.input}
          value={pw}
          onChange={(e) => { setPw(e.target.value); setError(""); }}
          onKeyDown={(e) => e.key === "Enter" && submit()}
          autoFocus
        />
        {error && <p style={gateStyles.error}>{error}</p>}
        <button style={gateStyles.button} onClick={submit} disabled={checking}>
          {checking ? "Kontrollerar …" : "Lås upp"}
        </button>
      </div>
    </div>
  );
}

const gateStyles = {
  wrap: { minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "'Inter', system-ui, sans-serif", background: "#F6F2EA", padding: "20px" },
  box: { background: "#FBF9F4", border: "1px solid #DED6C5", padding: "32px 26px", width: "100%", maxWidth: "300px", borderRadius: "6px" },
  title: { fontFamily: "'Source Serif 4', serif", fontSize: "24px", margin: "0 0 6px", textAlign: "center" },
  subtitle: { fontSize: "14px", color: "#8C8577", margin: "0 0 18px", textAlign: "center" },
  input: { width: "100%", boxSizing: "border-box", padding: "12px 14px", fontSize: "16px", border: "1px solid #DED6C5", borderRadius: "6px", marginBottom: "12px", outline: "none" },
  error: { fontSize: "13px", color: "#A14B2B", margin: "0 0 12px" },
  button: { width: "100%", background: "#4B5D42", color: "#F6F2EA", border: "none", padding: "12px", fontSize: "15px", cursor: "pointer", borderRadius: "6px", minHeight: "44px" },
};

function Root() {
  const [unlocked, setUnlocked] = useState(null);

  useEffect(() => {
    async function check() {
      try {
        const stored = localStorage.getItem("crm_unlocked_hash");
        if (!stored) {
          setUnlocked(false);
          return;
        }
        const res = await fetch("/api/login", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ hash: stored }),
        });
        const data = await res.json();
        if (data.ok) {
          AUTH_HASH = stored;
          setUnlocked(true);
        } else {
          setUnlocked(false);
        }
      } catch {
        setUnlocked(false);
      }
    }
    check();
  }, []);

  if (unlocked === null) return null;
  return unlocked ? <PersonalCRM /> : <PasswordGate onUnlock={() => setUnlocked(true)} />;
}

function PersonalCRM() {
  const [contacts, setContacts] = useState(null);
  const [view, setView] = useState("list");
  const [activeId, setActiveId] = useState(null);
  const [search, setSearch] = useState("");
  const [tagFilter, setTagFilter] = useState("Alla");
  const [draft, setDraft] = useState(null);
  const [meetingDraft, setMeetingDraft] = useState({ date: "", topics: "", notes: "" });
  const [error, setError] = useState("");
  const [saveState, setSaveState] = useState("idle");
  const [importText, setImportText] = useState("");
  const [importError, setImportError] = useState("");
  const [importSummary, setImportSummary] = useState("");
  const loaded = useRef(false);

  function replaceAllContacts() {
    try {
      const parsed = JSON.parse(importText);
      if (!Array.isArray(parsed)) throw new Error("Not an array");
      setContacts(parsed);
      setImportText("");
      setImportError("");
      setImportSummary(`Ersatte hela listan med ${parsed.length} kontakt(er) från texten.`);
    } catch {
      setImportError("Kunde inte läsa texten. Kontrollera att du klistrat in hela backupen oförändrad.");
    }
  }

  function importContacts() {
    const FILLABLE_FIELDS = [
      "role", "company", "previousCompanies", "email", "phone",
      "family", "interests", "allergies", "notes", "tag", "birthday",
    ];
    try {
      const parsed = JSON.parse(importText);
      if (!Array.isArray(parsed)) throw new Error("Not an array");
      let added = 0;
      let updated = 0;
      setContacts((prev) => {
        const byName = new Map(prev.map((c) => [c.name.toLowerCase(), c]));
        const result = [...prev];
        parsed.forEach((incoming) => {
          const key = (incoming.name || "").toLowerCase();
          const existing = byName.get(key);
          if (!existing) {
            result.push(incoming);
            added++;
            return;
          }
          let changed = false;
          const merged = { ...existing };
          FILLABLE_FIELDS.forEach((field) => {
            const incomingVal = (incoming[field] || "").toString().trim();
            const existingVal = (existing[field] || "").toString().trim();
            if (incomingVal && !existingVal) {
              merged[field] = incomingVal;
              changed = true;
            }
          });
          if (changed) {
            updated++;
            const idx = result.findIndex((c) => c.id === existing.id);
            result[idx] = merged;
          }
        });
        return result;
      });
      setImportText("");
      setImportError("");
      setImportSummary(`${added} ny(a) kontakt(er) tillagda, ${updated} befintlig(a) kompletterad(e) med saknad info.`);
    } catch {
      setImportError("Kunde inte läsa texten. Kontrollera att du klistrat in hela backupen oförändrad.");
    }
  }

  useEffect(() => {
    const link = document.createElement("link");
    link.rel = "stylesheet";
    link.href = FONT_IMPORT;
    document.head.appendChild(link);
    return () => document.head.removeChild(link);
  }, []);

  useEffect(() => {
    async function load() {
      try {
        const res = await apiFetch("/api/contacts");
        if (res.status === 401) {
          setContacts([]);
          loaded.current = true;
          return;
        }
        const data = await res.json();
        if (Array.isArray(data) && data.length > 0) {
          setContacts(data);
        } else {
          // Server has no contacts yet — seed once, then save that seed to the server.
          setContacts(SEED_CONTACTS);
          await apiFetch("/api/contacts", {
            method: "PUT",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(SEED_CONTACTS),
          });
        }
      } catch {
        setContacts([]);
      }
      loaded.current = true;
    }
    load();
  }, []);

  useEffect(() => {
    function handleBeforeUnload(e) {
      if (saveState === "saving") {
        e.preventDefault();
        e.returnValue = "";
      }
    }
    window.addEventListener("beforeunload", handleBeforeUnload);
    return () => window.removeEventListener("beforeunload", handleBeforeUnload);
  }, [saveState]);

  async function persist() {
    setSaveState("saving");
    try {
      const res = await apiFetch("/api/contacts", {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(contacts),
      });
      const ok = res.ok;
      setSaveState(ok ? "saved" : "error");
      if (ok) setTimeout(() => setSaveState("idle"), 1500);
    } catch {
      setSaveState("error");
    }
  }

  useEffect(() => {
    if (!loaded.current || contacts === null) return;
    persist();
  }, [contacts]);

  function retrySave() {
    persist();
  }

  if (contacts === null) {
    return (
      <div style={styles.loadingWrap}>
        <p style={styles.loadingText}>Läser in kontakter …</p>
      </div>
    );
  }

  const active = contacts.find((c) => c.id === activeId) || null;

  const filtered = contacts
    .filter((c) => tagFilter === "Alla" || c.tag === tagFilter)
    .filter((c) => {
      const q = search.toLowerCase();
      if (!q) return true;
      const meetingText = (c.meetings || []).map((m) => `${m.topics} ${m.notes}`).join(" ");
      const haystack = [
        c.name, c.company, c.role, c.previousCompanies, c.family,
        c.interests, c.allergies, c.notes, c.tag, meetingText,
      ].join(" ").toLowerCase();
      return haystack.includes(q);
    })
    .sort((a, b) => {
      const am = latestMeeting(a);
      const bm = latestMeeting(b);
      if (!am && !bm) return a.name.localeCompare(b.name);
      if (!am) return -1;
      if (!bm) return 1;
      return new Date(am.date) - new Date(bm.date);
    });

  function openContact(id) {
    setActiveId(id);
    setView("detail");
    setMeetingDraft({ date: new Date().toISOString().slice(0, 10), topics: "", notes: "" });
    setError("");
  }

  function startAdd() {
    setDraft(emptyContact());
    setView("add");
    setError("");
  }

  function startEdit(contact) {
    setDraft({ ...contact });
    setView("edit");
    setError("");
  }

  function saveDraft() {
    if (!draft.name.trim()) {
      setError("Ange ett namn.");
      return;
    }
    setContacts((prev) => {
      const exists = prev.some((c) => c.id === draft.id);
      if (exists) return prev.map((c) => (c.id === draft.id ? draft : c));
      return [...prev, draft];
    });
    setActiveId(draft.id);
    setView("detail");
    setMeetingDraft({ date: new Date().toISOString().slice(0, 10), topics: "", notes: "" });
  }

  function deleteContact(id) {
    setContacts((prev) => prev.filter((c) => c.id !== id));
    setView("list");
    setActiveId(null);
  }

  function addMeeting() {
    if (!meetingDraft.date) {
      setError("Ange ett datum för mötet.");
      return;
    }
    if (!meetingDraft.topics.trim()) {
      setError("Skriv vad samtalet handlade om.");
      return;
    }
    setError("");
    setContacts((prev) =>
      prev.map((c) =>
        c.id === activeId
          ? { ...c, meetings: [...c.meetings, { id: uid(), ...meetingDraft }] }
          : c
      )
    );
    setMeetingDraft({ date: new Date().toISOString().slice(0, 10), topics: "", notes: "" });
  }

  function deleteMeeting(meetingId) {
    setContacts((prev) =>
      prev.map((c) =>
        c.id === activeId
          ? { ...c, meetings: c.meetings.filter((m) => m.id !== meetingId) }
          : c
      )
    );
  }

  function updateContactFields(id, updates) {
    setContacts((prev) => prev.map((c) => (c.id === id ? { ...c, ...updates } : c)));
  }

  function setUntaggedToNatverk() {
    let count = 0;
    setContacts((prev) =>
      prev.map((c) => {
        if (!c.tag || !c.tag.trim()) {
          count++;
          return { ...c, tag: "Övrigt Nätverk" };
        }
        return c;
      })
    );
    return count;
  }

  return (
    <div style={styles.app}>
      {saveState === "saving" && (
        <div style={styles.saveBanner}>Sparar … vänta innan du stänger appen</div>
      )}
      {saveState === "saved" && <div style={styles.saveBannerOk}>Sparat ✓</div>}
      {saveState === "error" && (
        <div style={styles.saveBannerError}>
          Kunde inte spara — försök igen innan du stänger{" "}
          <button style={styles.retryButton} onClick={retrySave}>Försök igen</button>
        </div>
      )}
      {view === "list" && (
        <ListView
          contacts={filtered}
          allContacts={contacts}
          search={search}
          setSearch={setSearch}
          tagFilter={tagFilter}
          setTagFilter={setTagFilter}
          onOpen={openContact}
          onAdd={startAdd}
          saveState={saveState}
          onBackup={() => setView("backup")}
        />
      )}
      {view === "backup" && (
        <BackupView
          contacts={contacts}
          importText={importText}
          setImportText={setImportText}
          onImport={importContacts}
          onReplaceAll={replaceAllContacts}
          importError={importError}
          importSummary={importSummary}
          onBack={() => setView("list")}
          onSetUntaggedToNatverk={setUntaggedToNatverk}
        />
      )}
      {view === "detail" && active && (
        <DetailView
          contact={active}
          onBack={() => setView("list")}
          onEdit={() => startEdit(active)}
          onDelete={() => deleteContact(active.id)}
          meetingDraft={meetingDraft}
          setMeetingDraft={setMeetingDraft}
          onAddMeeting={addMeeting}
          onDeleteMeeting={deleteMeeting}
          onUpdateFields={updateContactFields}
          error={error}
          setError={setError}
        />
      )}
      {(view === "add" || view === "edit") && draft && (
        <EditView
          draft={draft}
          setDraft={setDraft}
          onSave={saveDraft}
          onCancel={() => setView(view === "add" ? "list" : "detail")}
          error={error}
          isNew={view === "add"}
        />
      )}
    </div>
  );
}

function ListView({ contacts, allContacts, search, setSearch, tagFilter, setTagFilter, onOpen, onAdd, saveState, onBackup }) {
  const upcomingBirthdays = contacts
    .filter((c) => c.birthday && daysUntilBirthday(c.birthday) <= 21)
    .sort((a, b) => daysUntilBirthday(a.birthday) - daysUntilBirthday(b.birthday));

  const REMINDER_THRESHOLDS = { "Familj & Vänner": 90, "Prio Nätverk": 180 };

  const overdueContacts = contacts.filter((c) => {
    const threshold = REMINDER_THRESHOLDS[c.tag];
    if (!threshold) return false;
    const lm = latestMeeting(c);
    const d = daysSince(lm && lm.date);
    return d === null || d > threshold;
  });

  const filterOptions = ["Alla", ...TAG_OPTIONS];

  return (
    <div>
      <div style={styles.headerRow}>
        <h1 style={styles.appTitle}>Kontakter</h1>
        <button style={styles.addButton} onClick={onAdd}>+ Ny kontakt</button>
      </div>
      <button style={styles.backupLink} onClick={onBackup}>Backup / återställ</button>
      <div style={styles.filterRow}>
        {filterOptions.map((opt) => {
          const count = opt === "Alla" ? allContacts.length : allContacts.filter((c) => c.tag === opt).length;
          const active = tagFilter === opt;
          return (
            <button
              key={opt}
              onClick={() => setTagFilter(opt)}
              style={{
                ...styles.filterPill,
                background: active ? "#4B5D42" : "#FBF9F4",
                color: active ? "#F6F2EA" : "#5F5A4E",
                borderColor: active ? "#4B5D42" : "#DED6C5",
              }}
            >
              {opt} ({count})
            </button>
          );
        })}
      </div>
      <input
        style={styles.searchInput}
        placeholder="Sök namn, roll, intressen, familj, mötesanteckningar..."
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />

      {upcomingBirthdays.length > 0 && (
        <div style={styles.reminderBox}>
          <p style={styles.reminderLabel}>Födelsedagar</p>
          {upcomingBirthdays.map((c) => {
            const d = daysUntilBirthday(c.birthday);
            return (
              <p key={c.id} style={styles.reminderLine} onClick={() => onOpen(c.id)}>
                {c.name} · {formatBirthday(c.birthday)} ({d === 0 ? "idag" : `om ${d} dagar`})
              </p>
            );
          })}
        </div>
      )}

      {overdueContacts.length > 0 && (
        <div style={styles.reminderBoxAmber}>
          <p style={styles.reminderLabelAmber}>Dags att höra av sig</p>
          {overdueContacts.map((c) => (
            <p key={c.id} style={styles.reminderLine} onClick={() => onOpen(c.id)}>
              {c.name} · {relTime(latestMeeting(c) && latestMeeting(c).date)}
            </p>
          ))}
        </div>
      )}

      {contacts.length === 0 && (
        <div style={styles.emptyState}>
          <p style={styles.emptyTitle}>Inga kontakter än</p>
          <p style={styles.emptyBody}>Lägg till den första personen du vill hålla koll på.</p>
        </div>
      )}
      <div>
        {contacts.map((c) => {
          const lm = latestMeeting(c);
          const d = daysSince(lm && lm.date);
          const overdue = REMINDER_THRESHOLDS[c.tag] && d !== null && d > REMINDER_THRESHOLDS[c.tag];
          return (
            <div key={c.id} style={styles.row} onClick={() => onOpen(c.id)}>
              <div style={styles.rowMain}>
                <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
                  <p style={styles.rowName}>{c.name}</p>
                  {c.tag && (
                    <span style={{ ...styles.tagBadge, color: TAG_COLORS[c.tag] || "#8C8577" }}>
                      {c.tag}
                    </span>
                  )}
                </div>
                <p style={styles.rowSub}>
                  {[c.role, c.company].filter(Boolean).join(", ") || "Ingen roll angiven"}
                </p>
              </div>
              <span style={{ ...styles.rowTime, color: overdue ? "#A14B2B" : "#8C8577" }}>
                {relTime(lm && lm.date)}
              </span>
            </div>
          );
        })}
      </div>
      <p style={styles.saveIndicator}>
        {saveState === "saving" ? "Sparar …" : saveState === "error" ? "Kunde inte spara" : ""}
      </p>
    </div>
  );
}

function DetailView({
  contact,
  onBack,
  onEdit,
  onDelete,
  meetingDraft,
  setMeetingDraft,
  onAddMeeting,
  onDeleteMeeting,
  onUpdateFields,
  error,
  setError,
}) {
  const [confirmDelete, setConfirmDelete] = useState(false);
  const [quickNote, setQuickNote] = useState("");
  const [processing, setProcessing] = useState(false);
  const [aiError, setAiError] = useState("");
  const [suggestions, setSuggestions] = useState(null);
  const [resultMessage, setResultMessage] = useState("");
  const lm = latestMeeting(contact);
  const meetings = [...contact.meetings].sort((a, b) => new Date(b.date) - new Date(a.date));

  const FIELD_LABELS = {
    role: "Roll", company: "Företag", previousCompanies: "Historik och tidigare arbetsplatser",
    phone: "Mobilnummer", family: "Familj", interests: "Intressen", allergies: "Allergier",
    notes: "Övrigt",
  };

  function mergeField(existing, incoming) {
    if (!incoming) return existing || "";
    if (!existing) return incoming;
    if (existing.toLowerCase().includes(incoming.toLowerCase())) return existing;
    return `${existing}; ${incoming}`;
  }

  async function structureNote() {
    if (!quickNote.trim()) {
      setAiError("Skriv eller klistra in några rader från mötet först.");
      return;
    }
    setProcessing(true);
    setAiError("");
    setSuggestions(null);
    setResultMessage("");
    try {
      const response = await apiFetch("/api/structure", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ note: quickNote }),
      });
      const parsed = await response.json();
      if (parsed.error) throw new Error(parsed.error);

      if (parsed.hasMeetingContent) {
        setMeetingDraft({
          ...meetingDraft,
          topics: parsed.topics || "",
          notes: (parsed.bullets || []).map((b) => `• ${b}`).join("\n"),
        });
      }

      const updates = parsed.updates || {};
      const proposed = {};
      Object.keys(FIELD_LABELS).forEach((key) => {
        if (updates[key] && updates[key].trim()) {
          proposed[key] = { value: mergeField(contact[key], updates[key].trim()), include: true };
        }
      });
      const hasProfileUpdates = Object.keys(proposed).length > 0;
      setSuggestions(hasProfileUpdates ? proposed : null);

      if (parsed.hasMeetingContent && hasProfileUpdates) {
        setResultMessage("Fyllde i mötesloggen nedan och föreslår även uppdateringar av kontaktkortet.");
      } else if (parsed.hasMeetingContent) {
        setResultMessage("Fyllde i mötesloggen nedan.");
      } else if (hasProfileUpdates) {
        setResultMessage("Ingen mötesanteckning behövdes — föreslår uppdateringar av kontaktkortet nedan istället.");
      } else {
        setResultMessage("Hittade ingen tydlig information att spara. Prova att skriva om.");
      }
      setQuickNote("");
    } catch {
      setAiError("Kunde inte strukturera anteckningen just nu. Fyll i fälten manuellt nedan.");
    }
    setProcessing(false);
  }

  return (
    <div>
      <div style={styles.detailNav}>
        <button style={styles.textButton} onClick={onBack}>&larr; Tillbaka</button>
        <div style={{ display: "flex", gap: "12px" }}>
          <button style={styles.textButton} onClick={onEdit}>Redigera</button>
          <button style={{ ...styles.textButton, color: "#A14B2B" }} onClick={() => setConfirmDelete(true)}>
            Ta bort
          </button>
        </div>
      </div>

      {confirmDelete && (
        <div style={styles.confirmBox}>
          <p style={styles.confirmText}>Ta bort {contact.name} permanent?</p>
          <div style={{ display: "flex", gap: "10px" }}>
            <button style={styles.smallButtonDanger} onClick={onDelete}>Ja, ta bort</button>
            <button style={styles.smallButton} onClick={() => setConfirmDelete(false)}>Avbryt</button>
          </div>
        </div>
      )}

      <div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
        <h1 style={styles.contactName}>{contact.name}</h1>
        {contact.tag && (
          <span style={{ ...styles.tagBadge, color: TAG_COLORS[contact.tag] || "#8C8577" }}>
            {contact.tag}
          </span>
        )}
      </div>
      <p style={styles.contactRole}>
        {[contact.role, contact.company].filter(Boolean).join(", ")}
      </p>

      <div style={styles.metaGrid}>
        {contact.birthday && (
          <MetaItem
            label="Födelsedag"
            value={`${formatBirthday(contact.birthday)} (om ${daysUntilBirthday(contact.birthday)} dagar)`}
          />
        )}
        {contact.phone && <MetaItem label="Mobilnummer" value={contact.phone} />}
        {contact.email && <MetaItem label="E-post" value={contact.email} />}
        {contact.previousCompanies && <MetaItem label="Historik och tidigare arbetsplatser" value={contact.previousCompanies} />}
        {contact.family && <MetaItem label="Familj" value={contact.family} />}
        {contact.interests && <MetaItem label="Intressen" value={contact.interests} />}
        {contact.allergies && <MetaItem label="Allergier" value={contact.allergies} />}
        {contact.notes && <MetaItem label="Övrigt" value={contact.notes} />}
      </div>

      {lm && (
        <div style={styles.briefBox}>
          <p style={styles.briefLabel}>Inför nästa möte</p>
          <p style={styles.briefDate}>Senast ni sågs: {formatDate(lm.date)} · {relTime(lm.date)}</p>
          <p style={styles.briefTopics}>{lm.topics}</p>
          {lm.notes && <p style={styles.briefNotes}>{lm.notes}</p>}
        </div>
      )}

      <div style={styles.logSection}>
        <p style={styles.sectionLabel}>Snabbanteckning</p>
        <p style={styles.quickNoteHint}>
          Klistra in eller diktera lösa anteckningar från mötet, så strukturerar Claude dem åt dig nedan.
        </p>
        <textarea
          style={{ ...styles.input, minHeight: "70px", resize: "vertical", marginBottom: "8px" }}
          placeholder="T.ex. pratade om hans nya jobb, dottern börjar gymnasiet till hösten, ville höra mer om vårt fondupplägg..."
          value={quickNote}
          onChange={(e) => setQuickNote(e.target.value)}
        />
        {aiError && <p style={styles.errorText}>{aiError}</p>}
        {resultMessage && <p style={styles.quickNoteHint}>{resultMessage}</p>}
        <button style={styles.primaryButton} onClick={structureNote} disabled={processing}>
          {processing ? "Strukturerar …" : "Strukturera med AI"}
        </button>

        {suggestions && (
          <div style={styles.suggestBox}>
            <p style={styles.suggestLabel}>Föreslagna uppdateringar av kontaktkortet</p>
            {Object.keys(suggestions).map((key) => (
              <div key={key} style={styles.suggestRow}>
                <label style={styles.suggestCheckboxRow}>
                  <input
                    type="checkbox"
                    checked={suggestions[key].include}
                    onChange={(e) =>
                      setSuggestions({
                        ...suggestions,
                        [key]: { ...suggestions[key], include: e.target.checked },
                      })
                    }
                  />
                  <span style={styles.suggestFieldLabel}>{FIELD_LABELS[key]}</span>
                </label>
                <input
                  style={styles.input}
                  value={suggestions[key].value}
                  onChange={(e) =>
                    setSuggestions({
                      ...suggestions,
                      [key]: { ...suggestions[key], value: e.target.value },
                    })
                  }
                />
              </div>
            ))}
            <div style={{ display: "flex", gap: "8px", marginTop: "6px" }}>
              <button
                style={styles.primaryButton}
                onClick={() => {
                  const applied = {};
                  Object.keys(suggestions).forEach((key) => {
                    if (suggestions[key].include) applied[key] = suggestions[key].value;
                  });
                  onUpdateFields(contact.id, applied);
                  setSuggestions(null);
                }}
              >
                Uppdatera kontaktkort
              </button>
              <button style={styles.smallButton} onClick={() => setSuggestions(null)}>
                Ignorera
              </button>
            </div>
          </div>
        )}
      </div>

      <div style={styles.logSection}>
        <p style={styles.sectionLabel}>Logga nytt möte</p>
        <div style={styles.logForm}>
          <input
            type="date"
            style={styles.input}
            value={meetingDraft.date}
            onChange={(e) => {
              setMeetingDraft({ ...meetingDraft, date: e.target.value });
              setError("");
            }}
          />
          <input
            style={styles.input}
            placeholder="Vad pratade ni om?"
            value={meetingDraft.topics}
            onChange={(e) => {
              setMeetingDraft({ ...meetingDraft, topics: e.target.value });
              setError("");
            }}
          />
          <textarea
            style={{ ...styles.input, minHeight: "60px", resize: "vertical" }}
            placeholder="Anteckningar (valfritt)"
            value={meetingDraft.notes}
            onChange={(e) => setMeetingDraft({ ...meetingDraft, notes: e.target.value })}
          />
          {error && <p style={styles.errorText}>{error}</p>}
          <button style={styles.primaryButton} onClick={onAddMeeting}>Spara mötet</button>
        </div>
      </div>

      <div style={styles.timeline}>
        <p style={styles.sectionLabel}>Historik</p>
        {meetings.length === 0 && <p style={styles.emptyBody}>Inga möten loggade än.</p>}
        {meetings.map((m) => (
          <div key={m.id} style={styles.timelineItem}>
            <div style={styles.timelineHeader}>
              <span style={styles.timelineDate}>{formatDate(m.date)}</span>
              <button style={styles.miniDelete} onClick={() => onDeleteMeeting(m.id)}>Ta bort</button>
            </div>
            <p style={styles.timelineTopics}>{m.topics}</p>
            {m.notes && <p style={styles.timelineNotes}>{m.notes}</p>}
          </div>
        ))}
      </div>
    </div>
  );
}

function MetaItem({ label, value }) {
  return (
    <div style={styles.metaItem}>
      <p style={styles.metaLabel}>{label}</p>
      <p style={styles.metaValue}>{value}</p>
    </div>
  );
}

function BackupView({ contacts, importText, setImportText, onImport, onReplaceAll, importError, importSummary, onBack, onSetUntaggedToNatverk }) {
  const [copied, setCopied] = useState(false);
  const [tagMessage, setTagMessage] = useState("");
  const [confirmReplace, setConfirmReplace] = useState(false);
  const exportText = JSON.stringify(contacts, null, 2);

  function copyToClipboard() {
    try {
      navigator.clipboard.writeText(exportText);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch {}
  }

  function handleSetUntagged() {
    const count = onSetUntaggedToNatverk();
    setTagMessage(count === 0 ? "Alla kontakter hade redan en relation satt." : `Satte "Övrigt Nätverk" på ${count} kontakt(er).`);
  }

  return (
    <div>
      <div style={styles.detailNav}>
        <button style={styles.textButton} onClick={onBack}>&larr; Tillbaka</button>
      </div>
      <h1 style={styles.contactName}>Backup</h1>
      <p style={styles.contactRole}>
        Kopiera texten nedan och spara den någonstans (t.ex. en anteckning eller ett mejl till dig själv) som säkerhetskopia av alla dina kontakter.
      </p>
      <textarea
        readOnly
        style={{ ...styles.input, minHeight: "160px", fontSize: "13px", fontFamily: "monospace", marginBottom: "8px" }}
        value={exportText}
        onFocus={(e) => e.target.select()}
      />
      <button style={styles.primaryButton} onClick={copyToClipboard}>
        {copied ? "Kopierat!" : "Kopiera till urklipp"}
      </button>

      <div style={{ marginTop: "32px" }}>
        <p style={styles.sectionLabel}>Underhåll</p>
        <p style={styles.quickNoteHint}>
          Sätter relationen "Övrigt Nätverk" på alla kontakter som saknar en relation just nu.
        </p>
        <button style={styles.smallButton} onClick={handleSetUntagged}>Sätt "Övrigt Nätverk" på alla utan relation</button>
        {tagMessage && <p style={styles.quickNoteHint}>{tagMessage}</p>}
      </div>

      <div style={{ marginTop: "32px" }}>
        <p style={styles.sectionLabel}>Återställ från backup</p>
        <p style={styles.quickNoteHint}>
          Klistra in en tidigare sparad backup-text här. Nya kontakter (nytt namn) läggs till. Kontakter som redan finns kompletteras — fält som är tomma hos dig fylls i från texten, men inget som redan har ett värde skrivs över.
        </p>
        <textarea
          style={{ ...styles.input, minHeight: "120px", fontSize: "13px", fontFamily: "monospace", marginBottom: "8px" }}
          placeholder="Klistra in backup-JSON här..."
          value={importText}
          onChange={(e) => { setImportText(e.target.value); setConfirmReplace(false); }}
        />
        {importError && <p style={styles.errorText}>{importError}</p>}
        <div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
          <button style={styles.smallButtonDanger} onClick={onImport}>Komplettera (säkert)</button>
          {!confirmReplace ? (
            <button style={styles.smallButton} onClick={() => setConfirmReplace(true)}>Ersätt allt med denna text …</button>
          ) : (
            <>
              <button
                style={styles.smallButtonDanger}
                onClick={() => { onReplaceAll(); setConfirmReplace(false); }}
              >
                Ja, radera allt och ersätt
              </button>
              <button style={styles.smallButton} onClick={() => setConfirmReplace(false)}>Avbryt</button>
            </>
          )}
        </div>
        {importSummary && <p style={styles.quickNoteHint}>{importSummary}</p>}
      </div>
    </div>
  );
}

function EditView({ draft, setDraft, onSave, onCancel, error, isNew }) {
  return (
    <div>
      <div style={styles.detailNav}>
        <button style={styles.textButton} onClick={onCancel}>Avbryt</button>
        <p style={styles.editTitle}>{isNew ? "Ny kontakt" : "Redigera"}</p>
        <button style={styles.textButton} onClick={onSave}>Spara</button>
      </div>
      <Field label="Namn" value={draft.name} onChange={(v) => setDraft({ ...draft, name: v })} />
      {error && <p style={styles.errorText}>{error}</p>}
      <Field label="Roll" value={draft.role} onChange={(v) => setDraft({ ...draft, role: v })} />
      <Field label="Företag" value={draft.company} onChange={(v) => setDraft({ ...draft, company: v })} />
      <Field label="Historik och tidigare arbetsplatser" value={draft.previousCompanies} onChange={(v) => setDraft({ ...draft, previousCompanies: v })} />
      <Field label="E-post" value={draft.email} onChange={(v) => setDraft({ ...draft, email: v })} />
      <Field label="Mobilnummer" value={draft.phone} onChange={(v) => setDraft({ ...draft, phone: v })} />
      <div style={styles.fieldWrap}>
        <label style={styles.fieldLabel}>Relation</label>
        <select
          style={styles.input}
          value={draft.tag}
          onChange={(e) => setDraft({ ...draft, tag: e.target.value })}
        >
          <option value="">Ingen</option>
          {TAG_OPTIONS.map((t) => (
            <option key={t} value={t}>{t}</option>
          ))}
        </select>
      </div>
      <div style={styles.fieldWrap}>
        <label style={styles.fieldLabel}>Födelsedag</label>
        <input
          type="date"
          style={styles.input}
          value={draft.birthday}
          onChange={(e) => setDraft({ ...draft, birthday: e.target.value })}
        />
      </div>
      <Field label="Familj" value={draft.family} onChange={(v) => setDraft({ ...draft, family: v })} multiline />
      <Field label="Intressen" value={draft.interests} onChange={(v) => setDraft({ ...draft, interests: v })} multiline />
      <Field label="Allergier" value={draft.allergies} onChange={(v) => setDraft({ ...draft, allergies: v })} multiline />
      <Field label="Övrigt" value={draft.notes} onChange={(v) => setDraft({ ...draft, notes: v })} multiline />
    </div>
  );
}

function Field({ label, value, onChange, multiline }) {
  return (
    <div style={styles.fieldWrap}>
      <label style={styles.fieldLabel}>{label}</label>
      {multiline ? (
        <textarea
          style={{ ...styles.input, minHeight: "60px", resize: "vertical" }}
          value={value}
          onChange={(e) => onChange(e.target.value)}
        />
      ) : (
        <input style={styles.input} value={value} onChange={(e) => onChange(e.target.value)} />
      )}
    </div>
  );
}

const styles = {
  app: {
    fontFamily: "'Inter', system-ui, sans-serif",
    color: "#2B2721",
    background: "#F6F2EA",
    padding: "20px 16px 48px",
    maxWidth: "560px",
    margin: "0 auto",
    minHeight: "300px",
    fontSize: "16px",
  },
  saveBanner: {
    background: "#F3E9DD",
    color: "#A14B2B",
    fontSize: "14px",
    textAlign: "center",
    padding: "10px",
    marginBottom: "12px",
    borderRadius: "4px",
  },
  saveBannerOk: {
    background: "#EDE7D8",
    color: "#4B5D42",
    fontSize: "14px",
    textAlign: "center",
    padding: "10px",
    marginBottom: "12px",
    borderRadius: "4px",
  },
  saveBannerError: {
    background: "#A14B2B",
    color: "#F6F2EA",
    fontSize: "14px",
    textAlign: "center",
    padding: "10px",
    marginBottom: "12px",
    borderRadius: "4px",
  },
  retryButton: {
    background: "#F6F2EA",
    color: "#A14B2B",
    border: "none",
    borderRadius: "4px",
    padding: "6px 12px",
    fontSize: "13px",
    cursor: "pointer",
    marginLeft: "6px",
    minHeight: "32px",
  },
  loadingWrap: { padding: "40px 16px", fontFamily: "'Inter', system-ui, sans-serif" },
  loadingText: { color: "#8C8577", fontSize: "16px" },
  headerRow: {
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    marginBottom: "18px",
    gap: "12px",
  },
  appTitle: {
    fontFamily: "'Source Serif 4', Georgia, serif",
    fontSize: "28px",
    fontWeight: 600,
    margin: 0,
  },
  addButton: {
    background: "#4B5D42",
    border: "none",
    borderRadius: "6px",
    padding: "10px 16px",
    fontSize: "15px",
    fontFamily: "'Inter', sans-serif",
    color: "#F6F2EA",
    cursor: "pointer",
    minHeight: "44px",
    whiteSpace: "nowrap",
  },
  searchInput: {
    width: "100%",
    boxSizing: "border-box",
    padding: "12px 14px",
    fontSize: "16px",
    fontFamily: "'Inter', sans-serif",
    border: "1px solid #DED6C5",
    borderRadius: "6px",
    background: "#FBF9F4",
    marginBottom: "12px",
    outline: "none",
  },
  backupLink: {
    background: "none",
    border: "none",
    fontSize: "14px",
    color: "#8C8577",
    textDecoration: "underline",
    cursor: "pointer",
    padding: "8px 0 16px",
    display: "block",
    minHeight: "36px",
  },
  filterRow: {
    display: "flex",
    gap: "8px",
    flexWrap: "wrap",
    marginBottom: "14px",
  },
  filterPill: {
    border: "1px solid #DED6C5",
    borderRadius: "20px",
    padding: "8px 14px",
    fontSize: "14px",
    fontFamily: "'Inter', sans-serif",
    cursor: "pointer",
    minHeight: "40px",
  },
  emptyState: { padding: "32px 4px" },
  emptyTitle: { fontFamily: "'Source Serif 4', serif", fontSize: "19px", margin: "0 0 6px" },
  emptyBody: { color: "#8C8577", fontSize: "15px", lineHeight: 1.5, margin: 0 },
  row: {
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    padding: "16px 4px",
    borderBottom: "1px solid #DED6C5",
    cursor: "pointer",
    minHeight: "44px",
  },
  rowMain: { display: "flex", flexDirection: "column", gap: "3px", minWidth: 0 },
  rowName: { fontFamily: "'Source Serif 4', serif", fontSize: "18px", margin: 0 },
  rowSub: { fontSize: "14px", color: "#8C8577", margin: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
  rowTime: { fontSize: "13px", whiteSpace: "nowrap", marginLeft: "12px" },
  saveIndicator: { fontSize: "13px", color: "#8C8577", textAlign: "right", marginTop: "12px", minHeight: "16px" },
  tagBadge: {
    fontSize: "12px",
    border: "1px solid currentColor",
    borderRadius: "4px",
    padding: "2px 8px",
    whiteSpace: "nowrap",
  },
  reminderBox: {
    background: "#EDE7D8",
    borderLeft: "3px solid #4B5D42",
    padding: "12px 16px",
    marginBottom: "12px",
    borderRadius: "0 4px 4px 0",
  },
  reminderLabel: { fontSize: "13px", color: "#4B5D42", fontWeight: 500, margin: "0 0 6px" },
  reminderBoxAmber: {
    background: "#F3E9DD",
    borderLeft: "3px solid #A14B2B",
    padding: "12px 16px",
    marginBottom: "18px",
    borderRadius: "0 4px 4px 0",
  },
  reminderLabelAmber: { fontSize: "13px", color: "#A14B2B", fontWeight: 500, margin: "0 0 6px" },
  reminderLine: { fontSize: "15px", margin: "6px 0", cursor: "pointer", lineHeight: 1.4 },
  detailNav: {
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    marginBottom: "20px",
    flexWrap: "wrap",
    gap: "10px",
  },
  textButton: {
    background: "none",
    border: "none",
    fontSize: "15px",
    fontFamily: "'Inter', sans-serif",
    color: "#2B2721",
    cursor: "pointer",
    padding: "8px 0",
    minHeight: "40px",
  },
  editTitle: { fontSize: "14px", color: "#8C8577", margin: 0 },
  confirmBox: {
    border: "1px solid #A14B2B",
    padding: "14px",
    marginBottom: "18px",
    borderRadius: "4px",
  },
  confirmText: { fontSize: "15px", margin: "0 0 12px" },
  smallButton: {
    background: "none",
    border: "1px solid #2B2721",
    padding: "8px 14px",
    fontSize: "14px",
    cursor: "pointer",
    borderRadius: "4px",
    minHeight: "40px",
  },
  smallButtonDanger: {
    background: "#A14B2B",
    color: "#F6F2EA",
    border: "1px solid #A14B2B",
    padding: "8px 14px",
    fontSize: "14px",
    cursor: "pointer",
    borderRadius: "4px",
    minHeight: "40px",
  },
  contactName: {
    fontFamily: "'Source Serif 4', Georgia, serif",
    fontSize: "30px",
    fontWeight: 600,
    margin: "0 0 4px",
    lineHeight: 1.2,
  },
  contactRole: { fontSize: "16px", color: "#8C8577", margin: "0 0 22px" },
  metaGrid: { display: "flex", flexDirection: "column", gap: "14px", marginBottom: "22px" },
  metaItem: { borderTop: "1px solid #DED6C5", paddingTop: "10px" },
  metaLabel: { fontSize: "13px", color: "#8C8577", margin: "0 0 4px" },
  metaValue: { fontSize: "16px", margin: 0, lineHeight: 1.5 },
  briefBox: {
    background: "#EDE7D8",
    borderLeft: "3px solid #4B5D42",
    padding: "16px 18px",
    marginBottom: "26px",
    borderRadius: "0 4px 4px 0",
  },
  briefLabel: { fontSize: "13px", color: "#4B5D42", fontWeight: 500, margin: "0 0 8px" },
  briefDate: { fontSize: "13px", color: "#5F5A4E", margin: "0 0 8px" },
  briefTopics: { fontSize: "16px", margin: "0 0 6px", lineHeight: 1.5 },
  briefNotes: { fontSize: "14px", color: "#5F5A4E", margin: 0, lineHeight: 1.6 },
  logSection: { marginBottom: "30px" },
  sectionLabel: {
    fontFamily: "'Source Serif 4', serif",
    fontSize: "18px",
    margin: "0 0 12px",
  },
  quickNoteHint: { fontSize: "14px", color: "#8C8577", margin: "0 0 10px", lineHeight: 1.5 },
  suggestBox: {
    background: "#EDE7D8",
    borderLeft: "3px solid #4B5D42",
    padding: "14px 16px",
    marginTop: "14px",
    borderRadius: "0 4px 4px 0",
  },
  suggestLabel: { fontSize: "14px", color: "#4B5D42", fontWeight: 500, margin: "0 0 12px" },
  suggestRow: { marginBottom: "12px" },
  suggestCheckboxRow: { display: "flex", alignItems: "center", gap: "8px", marginBottom: "6px" },
  suggestFieldLabel: { fontSize: "14px", color: "#5F5A4E" },
  logForm: { display: "flex", flexDirection: "column", gap: "10px" },
  input: {
    width: "100%",
    boxSizing: "border-box",
    padding: "12px 14px",
    fontSize: "16px",
    fontFamily: "'Inter', sans-serif",
    border: "1px solid #DED6C5",
    borderRadius: "6px",
    background: "#FBF9F4",
    outline: "none",
  },
  errorText: { fontSize: "14px", color: "#A14B2B", margin: "4px 0" },
  primaryButton: {
    alignSelf: "flex-start",
    background: "#4B5D42",
    color: "#F6F2EA",
    border: "none",
    padding: "12px 20px",
    fontSize: "15px",
    fontFamily: "'Inter', sans-serif",
    cursor: "pointer",
    borderRadius: "6px",
    minHeight: "44px",
  },
  timeline: { display: "flex", flexDirection: "column", gap: "16px" },
  timelineItem: { borderTop: "1px solid #DED6C5", paddingTop: "12px" },
  timelineHeader: { display: "flex", justifyContent: "space-between", alignItems: "center" },
  timelineDate: { fontSize: "13px", color: "#8C8577" },
  miniDelete: {
    background: "none",
    border: "none",
    fontSize: "13px",
    color: "#B4B2A9",
    cursor: "pointer",
    padding: "6px 0",
    minHeight: "32px",
  },
  timelineTopics: { fontSize: "16px", margin: "6px 0 3px", lineHeight: 1.5 },
  timelineNotes: { fontSize: "14px", color: "#8C8577", margin: 0, lineHeight: 1.6 },
  fieldWrap: { marginBottom: "16px" },
  fieldLabel: { display: "block", fontSize: "13px", color: "#8C8577", marginBottom: "6px" },
};
// version refresh 3

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