export const SWC_NOT_FOUND = Symbol('SWC_NOT_FOUND');

export class SwcUtils {
  static getValueByPath(obj: any, path: string, rootName: string) {
    if (!obj || !path) return SWC_NOT_FOUND;

    const parts = path.split('.');

    // 1. If the path starts with the rootName (alias), strip it and resolve within obj
    // e.g., getValueByPath({name: 'Kim'}, 'u.name', 'u') -> returns 'Kim'
    if (parts[0] === rootName && parts.length > 1) {
      const subParts = parts.slice(1);
      let subResult = obj;
      for (const part of subParts) {
        if (subResult === null || subResult === undefined || !(part in subResult)) return SWC_NOT_FOUND;
        subResult = subResult[part];
      }
      return subResult;
    }

    // 2. Fallback: resolve directly on obj (standard behavior)
    let result = obj;
    for (const part of parts) {
      if (result === null || result === undefined || !(part in result)) {
        return SWC_NOT_FOUND;
      }
      result = result[part];
    }

    // Safety: don't return the instance itself or a DOM element as a template value
    if (result !== obj && !(result instanceof HTMLElement)) {
      return result;
    }

    return SWC_NOT_FOUND;
  }

  static evalExpression(expr: string, context: Record<string, any>): any {
    try {
      const keys = Object.keys(context);
      const values = Object.values(context);
      // eslint-disable-next-line no-new-func
      return new Function(...keys, `return (${expr})`).apply(null, values);
    } catch {
      return SWC_NOT_FOUND;
    }
  }

  static applyData(
    node: Node,
    data: any,
    options: {
      asKey: string;
      asIndexKey: string;
      index?: number;
      hostContext?: Record<string, any>; // 부모 SWC 호스트의 name→value 컨텍스트
    }
  ) {
    const { asKey, asIndexKey, index, hostContext } = options;
    const context: Record<string, any> = {};
    if (index !== undefined) {
      context[asIndexKey] = index;
    }
    if (data !== null && data !== undefined) {
      context[asKey] = data;
    }
    // hostContext 병합 (로컬 컨텍스트가 우선)
    const fullContext: Record<string, any> = { ...(hostContext || {}), ...context };

    const resolveExpr = (expr: string, match: string): string => {
      expr = expr.trim();
      // 1. 로컬 데이터 단순 경로 (u.name 등)
      const simpleVal = SwcUtils.getValueByPath(data, expr, asKey);
      if (simpleVal !== SWC_NOT_FOUND && typeof simpleVal !== 'symbol') {
        return simpleVal === null || simpleVal === undefined ? '' : String(simpleVal);
      }
      // 2. fullContext로 표현식 평가 (index+1, u.active?'active':'inactive', superUser.name 등)
      const exprVal = SwcUtils.evalExpression(expr, fullContext);
      if (exprVal !== SWC_NOT_FOUND && typeof exprVal !== 'symbol') {
        return exprVal === null || exprVal === undefined ? '' : String(exprVal);
      }
      return match;
    };

    const walk = (n: Node) => {
      if (n.nodeType === Node.TEXT_NODE) {
        if (!(n as any)._original) (n as any)._original = n.textContent;
        let text = (n as any)._original;
        text = text.replace(/{{(.*?)}}/g, (match: string, path: string) => {
          return resolveExpr(path, match);
        });
        if (n.textContent !== text) n.textContent = text;
      } else if (n.nodeType === Node.ELEMENT_NODE) {
        const el = n as HTMLElement;
        Array.from(el.attributes).forEach(a => {
          if (!(a as any)._original) (a as any)._original = a.value;
          let val = (a as any)._original;
          val = val.replace(/{{(.*?)}}/g, (match: string, path: string) => {
            return resolveExpr(path, match);
          });
          if (a.value !== val) a.value = val;
        });
        el.childNodes.forEach(walk);
      }
    };
    walk(node);
  }

  static createReactiveProxy(target: any, onChange: () => void, onIndexChange?: (index: number, val: any) => void) {
    const makeRecursiveProxy = (obj: any): any => {
      if (typeof obj !== 'object' || obj === null || obj instanceof Node) return obj;
      if ((obj as any).__swc_is_proxy) return obj;

      const IS_PROXY_KEY = '__swc_is_proxy';

      const proxy = new Proxy(obj, {
        get: (t, prop) => {
          if (prop === IS_PROXY_KEY) return true;
          if (typeof prop === 'symbol') return t[prop];
          return t[prop];
        },
        set: (t, prop, val) => {
          if (prop === IS_PROXY_KEY) return true;
          if (typeof prop === 'symbol') { t[prop] = val; return true; }
          const oldVal = t[prop];
          if (oldVal === val) return true;
          t[prop] = makeRecursiveProxy(val);
          const isIndex = !isNaN(Number(prop)) && Array.isArray(t);
          if (isIndex && onIndexChange) {
            onIndexChange(Number(prop), val);
          } else {
            onChange();
          }
          return true;
        },
        deleteProperty: (t, prop) => {
          delete t[prop];
          onChange();
          return true;
        }
      });
      return proxy;
    };

    return makeRecursiveProxy(target);
  }

  static findNearestSwcHost(el: HTMLElement | Node): any {
    const hosts = this.findAllSwcHosts(el);
    return hosts.length > 0 ? hosts[0] : null;
  }

  static findAllSwcHosts(el: HTMLElement | Node): any[] {
    const hosts: any[] = [];
    let curr: any = el instanceof Node && !(el instanceof HTMLElement) ? (el as any).host : el;
    curr = curr?.parentElement || (curr?.getRootNode() as any)?.host;

    while (curr) {
      if (curr._swcId) hosts.push(curr);
      curr = curr.parentElement || (curr.getRootNode() as any).host;
    }
    return hosts.reverse();
  }
}
