/**
 * Universal reconciler for Solid custom renderers.
 *
 * Vendored from OpenTUI (MIT): https://github.com/miunau/opentui
 * packages/solid/src/renderer/universal.js
 *
 * Rewritten for Solid 2.0's two-phase createRenderEffect(compute, apply):
 *   - Reactive reads (signal access) happen in compute (tracked)
 *   - Tree mutations (insertNode, removeNode, etc.) happen in apply (untracked)
 *   - No accidental dependency tracking from mutation side-effects
 *   - mergeProps -> merge
 *
 * Converted to TypeScript with proper types.
 */
import { createComponent, createMemo, createRenderEffect, createRoot, merge, untrack } from "solid-js";

// Internal value type — the reconciler handles strings, numbers, arrays,
// functions, nodes, null, undefined, and booleans.

const memo = fn => createMemo(() => fn());
export function createRenderer(opts) {
  const {
    createElement,
    createTextNode,
    createSlotNode,
    isTextNode,
    replaceText,
    insertNode,
    removeNode,
    setProperty,
    getParentNode,
    getFirstChild,
    getNextSibling
  } = opts;
  function insert(parent, accessor, marker, initial) {
    if (marker !== undefined && !initial) initial = [];
    if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
    let current = initial;
    createRenderEffect(() => accessor(), value => {
      current = insertExpression(parent, value, current, marker);
    });
  }
  function insertExpression(parent, value, current, marker, unwrapArray) {
    while (typeof current === "function") current = current();
    if (value === current) return current;
    const t = typeof value;
    const multi = marker !== undefined;
    if (t === "string" || t === "number") {
      if (t === "number") value = String(value);
      if (multi) {
        let node = current[0];
        if (node && isTextNode(node)) {
          replaceText(node, value);
        } else node = createTextNode(value);
        current = cleanChildren(parent, current, marker, node);
      } else {
        if (current !== "" && typeof current === "string") {
          current = value;
          replaceText(getFirstChild(parent), current);
        } else {
          cleanChildren(parent, current, marker, createTextNode(value));
          current = value;
        }
      }
    } else if (value == null || t === "boolean") {
      current = cleanChildren(parent, current, marker);
    } else if (t === "function") {
      createRenderEffect(() => {
        let v = value();
        while (typeof v === "function") v = v();
        return v;
      }, v => {
        current = insertExpression(parent, v, current, marker);
      });
      return () => current;
    } else if (Array.isArray(value)) {
      const array = [];
      if (normalizeIncomingArray(array, value, unwrapArray)) {
        createRenderEffect(() => array.slice(), () => {
          current = insertExpression(parent, array, current, marker, true);
        });
        return () => current;
      }
      if (array.length === 0) {
        const replacement = cleanChildren(parent, current, marker);
        if (multi) {
          current = replacement;
          return current;
        }
      } else {
        if (Array.isArray(current)) {
          if (current.length === 0) {
            appendNodes(parent, array, marker);
          } else reconcileArrays(parent, current, array);
        } else if (current == null || current === "") {
          appendNodes(parent, array);
        } else {
          reconcileArrays(parent, multi && current || [getFirstChild(parent)], array);
        }
      }
      current = array;
    } else {
      if (Array.isArray(current)) {
        if (multi) {
          current = cleanChildren(parent, current, marker, value);
          return current;
        }
        cleanChildren(parent, current, null, value);
      } else if (current == null || current === "" || !getFirstChild(parent)) {
        insertNode(parent, value);
      } else replaceNode(parent, value, getFirstChild(parent));
      current = value;
    }
    return current;
  }
  function normalizeIncomingArray(normalized, array, unwrap) {
    let dynamic = false;
    for (let i = 0, len = array.length; i < len; i++) {
      let item = array[i];
      if (item == null || item === true || item === false) {
        // skip
      } else if (Array.isArray(item)) {
        dynamic = normalizeIncomingArray(normalized, item) || dynamic;
      } else if (typeof item === "string" || typeof item === "number") {
        normalized.push(createTextNode(String(item)));
      } else if (typeof item === "function") {
        if (unwrap) {
          while (typeof item === "function") item = item();
          dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item]) || dynamic;
        } else {
          normalized.push(item);
          dynamic = true;
        }
      } else normalized.push(item);
    }
    return dynamic;
  }

  // Array index access helper — indices in reconcileArrays are always
  // valid (guarded by loop bounds), avoids non-null assertions throughout.
  function at(arr, i) {
    return arr[i];
  }
  function reconcileArrays(parentNode, a, b) {
    const bLength = b.length;
    let aEnd = a.length;
    let bEnd = bLength;
    let aStart = 0;
    let bStart = 0;
    const after = getNextSibling(at(a, aEnd - 1));
    let map = null;
    while (aStart < aEnd || bStart < bEnd) {
      if (a[aStart] === b[bStart]) {
        aStart++;
        bStart++;
        continue;
      }
      while (a[aEnd - 1] === b[bEnd - 1]) {
        aEnd--;
        bEnd--;
      }
      if (aEnd === aStart) {
        const node = bEnd < bLength ? bStart ? getNextSibling(at(b, bStart - 1)) : b[bEnd - bStart] : after;
        while (bStart < bEnd) insertNode(parentNode, at(b, bStart++), node);
      } else if (bEnd === bStart) {
        while (aStart < aEnd) {
          if (!map?.has(at(a, aStart))) removeNode(parentNode, at(a, aStart));
          aStart++;
        }
      } else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
        const node = getNextSibling(at(a, --aEnd));
        insertNode(parentNode, at(b, bStart++), getNextSibling(at(a, aStart++)));
        insertNode(parentNode, at(b, --bEnd), node);
        a[aEnd] = at(b, bEnd);
      } else {
        if (!map) {
          map = new Map();
          let i = bStart;
          while (i < bEnd) {
            map.set(at(b, i), i);
            i++;
          }
        }
        const index = map.get(at(a, aStart));
        if (index != null) {
          if (bStart < index && index < bEnd) {
            let i = aStart;
            let sequence = 1;
            while (++i < aEnd && i < bEnd) {
              const t = map.get(at(a, i));
              if (t == null || t !== index + sequence) break;
              sequence++;
            }
            if (sequence > index - bStart) {
              const node = at(a, aStart);
              while (bStart < index) insertNode(parentNode, at(b, bStart++), node);
            } else replaceNode(parentNode, at(b, bStart++), at(a, aStart++));
          } else aStart++;
        } else removeNode(parentNode, at(a, aStart++));
      }
    }
  }
  function cleanChildren(parent, current, marker, replacement) {
    if (marker === undefined) {
      let removed = getFirstChild(parent);
      while (removed) {
        removeNode(parent, removed);
        removed = getFirstChild(parent);
      }
      if (replacement) insertNode(parent, replacement);
      return replacement ?? "";
    }
    const node = replacement || createSlotNode();
    if (Array.isArray(current) && current.length) {
      let inserted = false;
      for (let i = current.length - 1; i >= 0; i--) {
        const el = current[i];
        if (node !== el) {
          const hasParent = getParentNode(el) === parent;
          if (!inserted && !i) hasParent ? replaceNode(parent, node, el) : insertNode(parent, node, marker);else if (hasParent) removeNode(parent, el);
        } else inserted = true;
      }
    } else insertNode(parent, node, marker);
    return [node];
  }
  function appendNodes(parent, array, marker) {
    for (let i = 0, len = array.length; i < len; i++) insertNode(parent, at(array, i), marker);
  }
  function replaceNode(parent, newNode, oldNode) {
    insertNode(parent, newNode, oldNode);
    removeNode(parent, oldNode);
  }
  function spreadExpression(node, props, prevProps = {}, skipChildren) {
    if (!props) props = {};
    if (!skipChildren) {
      let current = prevProps.children;
      createRenderEffect(() => props.children, children => {
        current = insertExpression(node, children, current);
        prevProps.children = current;
      });
    }
    createRenderEffect(() => props.ref, ref => ref?.(node));
    createRenderEffect(() => {
      const snapshot = {};
      for (const prop in props) {
        if (prop === "children" || prop === "ref") continue;
        snapshot[prop] = props[prop];
      }
      return snapshot;
    }, newProps => {
      for (const prop in newProps) {
        const value = newProps[prop];
        if (value === prevProps[prop]) continue;
        setProperty(node, prop, value, prevProps[prop]);
        prevProps[prop] = value;
      }
    });
    return prevProps;
  }
  return {
    render(code, element) {
      let disposer;
      createRoot(dispose => {
        disposer = dispose;
        insert(element, code());
      });
      return disposer;
    },
    insert,
    spread(node, accessor, skipChildren) {
      if (typeof accessor === "function") {
        let prev;
        createRenderEffect(() => accessor(), props => {
          prev = spreadExpression(node, props, prev, skipChildren);
        });
      } else spreadExpression(node, accessor, undefined, skipChildren);
    },
    createElement,
    createTextNode,
    insertNode,
    setProp(node, name, value, prev) {
      setProperty(node, name, value, prev);
      return value;
    },
    mergeProps: merge,
    effect: createRenderEffect,
    memo,
    createComponent,
    ref(getter, node) {
      const ref = getter();
      if (typeof ref === "function") {
        ref(node);
      }
    },
    use(fn, element, arg) {
      return untrack(() => fn(element, arg));
    }
  };
}
//# sourceMappingURL=universal.jsx.map
