<script>
  import { onMount } from "svelte";

  export let block;
  export let onChange;

  let textareaEl;
  let showPreview = false;
  let showAdv = false;
  const update = (patch) => onChange({ ...block.data, ...patch });
  let measureCtx;

  const PREPOSITIONS = [
    "в",
    "во",
    "без",
    "до",
    "для",
    "за",
    "из",
    "изо",
    "к",
    "ко",
    "на",
    "над",
    "о",
    "об",
    "обо",
    "от",
    "ото",
    "по",
    "под",
    "подо",
    "при",
    "про",
    "ради",
    "с",
    "со",
    "у",
    "через",
    "черезо",
    "между",
    "перед",
    "пред",
    "около",
    "после",
    "вне"
  ];
  const PARTICLES = ["не"];
  const CONJUNCTIONS = ["и", "а", "но", "да", "или"];
  const HANG_BREAK = ["и", "а", "но"]; // только короткие союзы для переноса строки
  const HANG_WORDS = [...CONJUNCTIONS, ...PARTICLES, ...PREPOSITIONS];

  function ensureMeasureCtx() {
    if (!measureCtx) {
      const canvas = document.createElement("canvas");
      measureCtx = canvas.getContext("2d");
    }
    measureCtx.font = "18px Helvetica, Arial, sans-serif";
  }

  function resizeTextarea() {
    if (!textareaEl) return;
    textareaEl.style.height = "auto";
    textareaEl.style.height = `${textareaEl.scrollHeight}px`;
  }

  function handleInput(e) {
    const ta = e.target;
    const raw = ta.value;
    const newlineBeforePos = (raw.slice(0, ta.selectionStart ?? raw.length).match(/\n/g) || []).length;
    const normalized = raw.replace(/\r?\n/g, "<br>");
    if (normalized !== raw) {
      ta.value = normalized;
      const pos = (ta.selectionStart ?? normalized.length) + newlineBeforePos * 3;
      ta.selectionStart = ta.selectionEnd = pos;
    }
    update({ text: normalized });
    resizeTextarea();
  }

  function insertBr() {
    if (!textareaEl) return;
    const ta = textareaEl;
    const insert = "<br>";
    const start = ta.selectionStart ?? ta.value.length;
    const end = ta.selectionEnd ?? start;
    ta.value = ta.value.slice(0, start) + insert + ta.value.slice(end);
    const pos = start + insert.length;
    ta.selectionStart = ta.selectionEnd = pos;
    ta.focus();
    update({ text: ta.value });
    resizeTextarea();
  }

  function insertMdash() {
    if (!textareaEl) return;
    const ta = textareaEl;
    const insert = "&mdash;";
    const start = ta.selectionStart ?? ta.value.length;
    const end = ta.selectionEnd ?? start;
    ta.value = ta.value.slice(0, start) + insert + ta.value.slice(end);
    const pos = start + insert.length;
    ta.selectionStart = ta.selectionEnd = pos;
    ta.focus();
    update({ text: ta.value });
    resizeTextarea();
  }

  function makeBold() {
    if (!textareaEl) return;
    const ta = textareaEl;
    const start = ta.selectionStart ?? 0;
    const end = ta.selectionEnd ?? 0;
    const before = ta.value.slice(0, start);
    const selected = ta.value.slice(start, end);
    const after = ta.value.slice(end);
    const open = '<span style="font-weight:700;">';
    const close = "</span>";
    let next;

    if (start !== end) {
      next = before + open + selected + close + after;
      ta.value = next;
      ta.selectionStart = start + open.length;
      ta.selectionEnd = start + open.length + selected.length;
    } else {
      const insert = open + close;
      next = before + insert + after;
      ta.value = next;
      const pos = start + open.length;
      ta.selectionStart = ta.selectionEnd = pos;
    }
    ta.focus();
    update({ text: ta.value });
    resizeTextarea();
  }

  function applyTypograph() {
    if (!textareaEl) return;
    const ta = textareaEl;
    let text = ta.value;

    text = text
      .replace(/\.\.\./g, "…")
      .replace(/(^|[\s(])"([^"]+)"/g, '$1«$2»')
      .replace(/--/g, "—")
      .replace(/\s-\s/g, " &mdash; ")
      .replace(
        new RegExp(`(^|[\\s(])(${[...PREPOSITIONS, ...PARTICLES, ...CONJUNCTIONS].join("|")})\\s+`, "giu"),
        (_, prefix, word) => `${prefix}${word}&nbsp;`
      )
      .replace(/\u00A0\s+/g, "\u00A0")
      .replace(/ {2,}/g, " ")
      .replace(/\s+,/g, ",")
      .replace(/\s+\./g, ".")
      .replace(/\s+!/g, "!")
      .replace(/\s+\?/g, "?")
      .replace(/\s+:/g, ":");

    ta.value = text;
    const pos = ta.selectionEnd ?? text.length;
    ta.selectionStart = ta.selectionEnd = pos;
    ta.focus();
    update({ text });
    resizeTextarea();
  }

  function autoWrapBr() {
    if (!textareaEl) return;
    ensureMeasureCtx();

    const NBSP = "\u00A0";
    const limit = 520; // preview width minus padding
    const spaceWidth = measureCtx.measureText(" ").width;

    const raw = textareaEl.value
      .replace(/&nbsp;/g, NBSP)
      .replace(/\u00A0\s+/g, NBSP)
      .replace(/ {2,}/g, " ")
      .trim();
    if (!raw) return;

    const tokens = raw.split(/(<[^>]+>)/).filter(Boolean);
    const lines = [];
    const linesTokens = [];
    let currentTokens = [];
    let currentWidth = 0;

    const pushLine = () => {
      const lineStr = currentTokens.join("").trimEnd().replace(new RegExp(NBSP, "g"), "&nbsp;");
      lines.push(lineStr);
      linesTokens.push([...currentTokens]);
      currentTokens = [];
      currentWidth = 0;
    };

    const isTag = (t) => /^<[^>]+>$/.test(t);
    const hasTrailingSpace = () => {
      for (let i = currentTokens.length - 1; i >= 0; i--) {
        const t = currentTokens[i];
        if (isTag(t)) continue;
        return /[ \u00A0]$/.test(t);
      }
      return false;
    };

    const getLastWordToken = () => {
      for (let i = currentTokens.length - 1; i >= 0; i--) {
        const t = currentTokens[i];
        if (isTag(t)) continue;
        const trimmed = t.trim();
        if (trimmed) {
          return { index: i, token: t };
        }
      }
      return null;
    };

    tokens.forEach((token) => {
      const isBr = /^<\s*br\s*\/?\s*>$/i.test(token);
      if (isBr) {
        if (currentTokens.length) pushLine();
        return;
      }

      if (isTag(token)) {
        currentTokens.push(token);
        return;
      }

      const textChunk = token;
      // Разбиваем только по обычным пробелам/переводам строк, NBSP остаётся внутри слов
      const words = textChunk.split(/[ \t\r\n]+/).filter(Boolean);

      words.forEach((word) => {
        const plain = word.replace(new RegExp(NBSP, "g"), " ");
        const wordWidth = measureCtx.measureText(plain).width;

        // Знаки препинания присоединяем к предыдущему слову без пробела
        const isPunct = /^[-–—!?.,:;]+$/.test(plain.trim());
        if (isPunct) {
          if (currentTokens.length) {
            currentTokens.push(word);
            currentWidth += wordWidth;
            return;
          }
          if (lines.length) {
            const last = lines.pop() || "";
            lines.push((last + word).replace(new RegExp(NBSP, "g"), "&nbsp;"));
            return;
          }
          currentTokens.push(word);
          currentWidth += wordWidth;
          return;
        }

        const needSpace = currentTokens.length > 0 && !hasTrailingSpace();
        const nextWidth = currentWidth + (needSpace ? spaceWidth : 0) + wordWidth;

        if (nextWidth > limit && currentTokens.length) {
          // если последним словом был висячий — переносим его на новую строку
          const lastWord = getLastWordToken();
          let carried = null;
          if (lastWord) {
            const lastPlain = lastWord.token.replace(new RegExp(NBSP, "g"), " ").trim().toLowerCase();
            if (HANG_WORDS.includes(lastPlain)) {
              // убираем последний токен и возможный пробел перед ним
              currentTokens.splice(lastWord.index, 1);
              currentWidth -= measureCtx.measureText(lastPlain).width;
              if (currentTokens.length && currentTokens[currentTokens.length - 1] === " ") {
                currentTokens.pop();
                currentWidth -= spaceWidth;
              }
              carried = lastWord.token;
            }
          }

          pushLine();

          if (carried) {
            currentTokens.push(carried);
            currentWidth += measureCtx.measureText(
              carried.replace(new RegExp(NBSP, "g"), " ")
            ).width;
          }
        }

        if (needSpace && currentTokens.length) {
          currentTokens.push(" ");
          currentWidth += spaceWidth;
        }

        currentTokens.push(word);
        currentWidth += wordWidth;
      });
    });

    if (currentTokens.length) pushLine();

    const countWords = (tokens) =>
      tokens.reduce((acc, t) => {
        if (!isTag(t) && t.trim()) return acc + 1;
        return acc;
      }, 0);

    const popLastWord = (tokens) => {
      let collected = [];
      while (tokens.length) {
        const t = tokens.pop();
        if (isTag(t)) {
          collected.unshift(t);
          continue;
        }
        if (!t.trim()) {
          continue;
        }
        collected.unshift(t);
        break;
      }
      while (tokens.length && !tokens[tokens.length - 1].trim()) {
        tokens.pop();
      }
      return collected.length ? collected : null;
    };

    if (linesTokens.length >= 2) {
      const lastTokens = linesTokens[linesTokens.length - 1];
      const prevTokens = linesTokens[linesTokens.length - 2];

      if (countWords(lastTokens) === 1 && countWords(prevTokens) > 1) {
        const moved = popLastWord(prevTokens);
        if (moved) {
          if (prevTokens.length && !/[ \u00A0]$/.test(prevTokens[prevTokens.length - 1] || "")) {
            moved.unshift(" ");
          }
          lastTokens.unshift(...moved);
        }
      }

      for (let i = 0; i < linesTokens.length; i++) {
        lines[i] = linesTokens[i].join("").trimEnd().replace(new RegExp(NBSP, "g"), "&nbsp;");
      }
    }

    const wrapped = lines.join("<br>");
    const clean = wrapped.replace(/\s*<br\s*\/?>\s*/gi, "<br>");

    textareaEl.value = clean;
    const pos = textareaEl.value.length;
    textareaEl.selectionStart = textareaEl.selectionEnd = pos;
    textareaEl.focus();
    update({ text: clean });
    resizeTextarea();
  }

  onMount(() => {
    resizeTextarea();
  });
</script>

<label>
  Текст блока
  <textarea
    bind:this={textareaEl}
    class="paragraph-textarea"
    value={block.data.text ?? ""}
    on:input={handleInput}
  />
</label>

<div class="btn-inline-row">
  <button class="btn-inline" type="button" on:click={insertBr}>&lt;br&gt;</button>
  <button class="btn-inline" type="button" on:click={makeBold}>Жирный</button>
  <button class="btn-inline" type="button" on:click={insertMdash}>&mdash;</button>
  <button class="btn-inline" type="button" on:click={applyTypograph}>Типограф</button>
  <button class="btn-inline" type="button" on:click={autoWrapBr}>Авто &lt;br&gt;</button>
  <button
    class="btn-inline push-right"
    class:active={showPreview}
    type="button"
    aria-pressed={showPreview}
    on:click={() => (showPreview = !showPreview)}
  >
    HTML
  </button>
</div>
{#if showPreview}
  <div class="block-preview paragraph-preview" aria-live="polite">
    {@html block.data.text || ""}
  </div>
{/if}

<div class="advanced-wrapper">
  <button class="btn-advanced-toggle" class:is-open={showAdv} type="button" on:click={() => (showAdv = !showAdv)}>
    Доп. настройки
  </button>
  {#if showAdv}
    <div class="advanced-panel">
      <label>
        Отступ снизу (px)
        <input
          type="number"
          value={block.data.bottomSpacing ?? 40}
          on:input={(e) => update({ bottomSpacing: parseInt(e.target.value, 10) || 0 })}
        />
      </label>
    </div>
  {/if}
</div>

<label class="inline">
  <input
    type="checkbox"
    checked={block.data.removeBottomSpacing}
    on:change={(e) => update({ removeBottomSpacing: e.target.checked })}
  />
  Убрать отступ после блока
</label>
