/**
 * Property editor, @var management, autocomplete, input handling.
 * Merged into Vue app via: methods: { ...PropMethods, ... }
 */

const PropMethods = {
  // ── Property get/set ──

  getArg(idx) {
    const el = this.selectedElement;
    return el && el.args ? (el.args[idx] || '') : '';
  },

  setArg(idx, val) {
    const el = this.selectedElement;
    if (!el || !el.args) return;
    while (el.args.length <= idx) el.args.push('0');
    el.args[idx] = val;
    this.onPropChange();
  },

  addShapeVertex() {
    const el = this.selectedElement;
    if (!el || el.type !== 'shape') return;
    this.pushUndo();
    el.args.push('50', '50');
    this.syncCodeFromFace();
    this.renderCanvas();
  },

  removeShapeVertex() {
    const el = this.selectedElement;
    if (!el || el.type !== 'shape' || el.args.length <= 6) return;
    this.pushUndo();
    el.args.splice(-2, 2);
    this.syncCodeFromFace();
    this.renderCanvas();
  },

  getProp(key) {
    const el = this.selectedElement;
    return el && el.props ? (el.props[key] || '') : '';
  },

  setProp(key, val) {
    const el = this.selectedElement;
    if (!el || !el.props) return;
    if (val === '' || val == null) { delete el.props[key]; }
    else { el.props[key] = val; }
    this.onPropChange();
  },

  hasProp(key) {
    const el = this.selectedElement;
    return el && el.props && el.props[key] != null;
  },

  toggleProp(key, checked) {
    const el = this.selectedElement;
    if (!el || !el.props) return;
    if (checked) el.props[key] = 'true';
    else delete el.props[key];
    this.onPropChange();
  },

  propToHex(val) {
    if (!val) return '#ffffff';
    if (val.startsWith('#') && (val.length === 7 || val.length === 4)) return val;
    if (val.startsWith('$') || /^[a-z]+$/i.test(val)) return '#888888';
    return val;
  },

  onPropChange() {
    this.pushUndoDebounced();
    this.syncCodeFromFace();
    this.renderCanvas();
    this.autoSaveSession();
  },

  // ── @var management ──

  addVar() {
    if (!this.face.vars) this.face.vars = {};
    const name = '$var' + (Object.keys(this.face.vars).length + 1);
    this.face.vars[name] = '0';
    this.pushUndo();
    this.syncCodeFromFace();
    this.renderCanvas();
  },

  removeVar(idx) {
    const entries = Object.entries(this.face.vars || {});
    if (idx < 0 || idx >= entries.length) return;
    this.pushUndo();
    delete this.face.vars[entries[idx][0]];
    this.syncCodeFromFace();
    this.renderCanvas();
  },

  onVarsChange() {
    const newVars = {};
    for (const v of this.varEntries) {
      if (v.name && v.name.startsWith('$')) {
        newVars[v.name] = v.expr || '0';
      }
    }
    this.face.vars = newVars;
    this.pushUndoDebounced();
    this.syncCodeFromFace();
    this.renderCanvas();
  },

  // ── Autocomplete ──

  checkAutocomplete(e, type, key) {
    const input = e.target;
    const val = input.value;
    const pos = input.selectionStart;
    if (pos == null) { this.autocomplete.show = false; return; }
    const before = val.substring(0, pos);

    const ALL_VARS = [
      { name: '$value', desc: 'Valeur formatee' },
      { name: '$unit', desc: 'Unite' },
      { name: '$label', desc: 'Nom capteur' },
      { name: '$raw', desc: 'Valeur brute' },
      { name: '$percent', desc: 'Pourcentage' },
      { name: '$progress', desc: '0.0 a 1.0' },
      { name: '$min', desc: 'Minimum' },
      { name: '$max', desc: 'Maximum' },
      { name: '$accent', desc: 'Couleur accent' },
      { name: '$zone_color', desc: 'Couleur zone' },
      { name: '$zone', desc: 'normal/warning/danger' },
      { name: '$whp', desc: 'Seuil warning haut' },
      { name: '$wlp', desc: 'Seuil warning bas' },
      { name: '$dhp', desc: 'Seuil danger haut' },
      { name: '$dlp', desc: 'Seuil danger bas' },
      { name: '$time', desc: 'Temps en secondes (animation 60fps)' },
    ];
    if (this.face.vars) {
      for (const name of Object.keys(this.face.vars)) {
        if (!ALL_VARS.find(v => v.name === name)) {
          ALL_VARS.push({ name, desc: 'Variable custom' });
        }
      }
    }
    // Add data binding variables (multi-value gauges)
    if (this.dataBindings && this.dataBindings.length > 0) {
      for (let bi = 0; bi < this.dataBindings.length; bi++) {
        const b = this.dataBindings[bi];
        const bt = PREVIEW_TYPES.find(t => t.key === b.type);
        const lbl = bt ? bt.label : b.type;
        const idx = bi + 1;
        // Named by type
        ALL_VARS.push({ name: '$value-' + b.type, desc: lbl + ' formatee' });
        ALL_VARS.push({ name: '$progress-' + b.type, desc: lbl + ' 0.0-1.0' });
        ALL_VARS.push({ name: '$raw-' + b.type, desc: lbl + ' brute' });
        ALL_VARS.push({ name: '$zone-' + b.type, desc: lbl + ' zone' });
        ALL_VARS.push({ name: '$unit-' + b.type, desc: lbl + ' unite' });
        ALL_VARS.push({ name: '$label-' + b.type, desc: lbl + ' nom' });
        ALL_VARS.push({ name: '$accent-' + b.type, desc: lbl + ' couleur' });
        ALL_VARS.push({ name: '$min-' + b.type, desc: lbl + ' minimum' });
        ALL_VARS.push({ name: '$max-' + b.type, desc: lbl + ' maximum' });
        // Indexed
        ALL_VARS.push({ name: '$value-' + idx, desc: 'Binding #' + idx + ' valeur' });
        ALL_VARS.push({ name: '$progress-' + idx, desc: 'Binding #' + idx + ' progress' });
        ALL_VARS.push({ name: '$raw-' + idx, desc: 'Binding #' + idx + ' brute' });
        ALL_VARS.push({ name: '$zone-' + idx, desc: 'Binding #' + idx + ' zone' });
        ALL_VARS.push({ name: '$unit-' + idx, desc: 'Binding #' + idx + ' unite' });
        ALL_VARS.push({ name: '$label-' + idx, desc: 'Binding #' + idx + ' nom' });
        ALL_VARS.push({ name: '$accent-' + idx, desc: 'Binding #' + idx + ' couleur' });
      }
    }

    const ALL_FUNCS = [
      { name: 'clamp(', desc: 'clamp(val, min, max)' },
      { name: 'min(', desc: 'min(a, b)' },
      { name: 'max(', desc: 'max(a, b)' },
      { name: 'abs(', desc: 'abs(val)' },
      { name: 'round(', desc: 'round(val)' },
      { name: 'floor(', desc: 'floor(val)' },
      { name: 'ceil(', desc: 'ceil(val)' },
      { name: 'lerp(', desc: 'lerp(a, b, t)' },
      { name: 'sin(', desc: 'sin(deg)' },
      { name: 'cos(', desc: 'cos(deg)' },
      { name: 'mod(', desc: 'mod(a, b)' },
      { name: 'pow(', desc: 'pow(base, exp)' },
      { name: 'iif(', desc: 'iif(cond, vrai, faux)' },
    ];

    // Try $variable match
    const dollarIdx = before.lastIndexOf('$');
    if (dollarIdx >= 0 && (dollarIdx === 0 || /[\s"(+\-*/,=]/.test(before[dollarIdx - 1]))) {
      const query = before.substring(dollarIdx);
      const filtered = ALL_VARS.filter(v => v.name.startsWith(query));
      if (filtered.length > 0 && query !== filtered[0].name) {
        const rect = input.getBoundingClientRect();
        this.autocomplete = {
          show: true, x: rect.left, y: rect.bottom + 2,
          items: filtered, matchStart: dollarIdx,
          target: input, targetType: type, targetKey: key
        };
        return;
      }
    }

    // Try function name match
    const funcMatch = before.match(/(?:^|[\s(+\-*/,=])([a-z]{2,})$/);
    if (funcMatch) {
      const query = funcMatch[1];
      const matchStart = before.length - query.length;
      const filtered = ALL_FUNCS.filter(f => f.name.startsWith(query));
      if (filtered.length > 0 && query !== filtered[0].name) {
        const rect = input.getBoundingClientRect();
        this.autocomplete = {
          show: true, x: rect.left, y: rect.bottom + 2,
          items: filtered, matchStart,
          target: input, targetType: type, targetKey: key
        };
        return;
      }
    }

    this.autocomplete.show = false;
  },

  insertAutocomplete(itemName) {
    const ac = this.autocomplete;
    const input = ac.target;
    if (!input) return;
    const val = input.value;
    const pos = input.selectionStart || val.length;
    const before = val.substring(0, ac.matchStart);
    const after = val.substring(pos);
    const newVal = before + itemName + after;
    input.value = newVal;
    if (ac.targetType === 'arg') this.setArg(ac.targetKey, newVal);
    else if (ac.targetType === 'prop') this.setProp(ac.targetKey, newVal);
    else if (ac.targetType === 'var-expr') {
      this.varEntries[ac.targetKey].expr = newVal;
      this.onVarsChange();
    } else if (ac.targetType === 'var-name') {
      this.varEntries[ac.targetKey].name = newVal;
      this.onVarsChange();
    }
    this.autocomplete.show = false;
    input.focus();
    const newPos = (before + itemName).length;
    input.setSelectionRange(newPos, newPos);
  },

  /** Syntax-highlight an expression string (single-pass tokenizer). */
  hlExpr(text) {
    if (!text) return '';
    const e = c => c.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
    let r = '', i = 0, t = String(text);
    while (i < t.length) {
      if (t[i] === '$') {
        const m = t.substring(i).match(/^\$\w+/);
        if (m) { r += '<span class="hl-v">' + e(m[0]) + '</span>'; i += m[0].length; continue; }
      }
      if (t[i] >= 'a' && t[i] <= 'z') {
        const m = t.substring(i).match(/^(clamp|min|max|abs|round|floor|ceil|lerp|sin|cos|mod|pow|iif)\b/);
        if (m) { r += '<span class="hl-f">' + m[0] + '</span>'; i += m[0].length; continue; }
      }
      if (t[i] === '#') {
        const m = t.substring(i).match(/^#[0-9A-Fa-f]{3,8}\b/);
        if (m) { r += '<span class="hl-c">' + m[0] + '</span>'; i += m[0].length; continue; }
      }
      if (t[i] === '"') {
        const end = t.indexOf('"', i + 1);
        if (end >= 0) { r += '<span class="hl-s">' + e(t.substring(i, end + 1)) + '</span>'; i = end + 1; continue; }
      }
      let op = null;
      if (t.substring(i,i+2)==='==' || t.substring(i,i+2)==='!=' || t.substring(i,i+2)==='>=' || t.substring(i,i+2)==='<=' || t.substring(i,i+2)==='&&' || t.substring(i,i+2)==='||') op = t.substring(i,i+2);
      else if (t[i]==='>' || t[i]==='<') op = t[i];
      if (op) { r += '<span class="hl-op">' + e(op) + '</span>'; i += op.length; continue; }
      r += e(t[i]); i++;
    }
    return r;
  },

  hideAutocomplete() {
    setTimeout(() => { this.autocomplete.show = false; }, 150);
  },

  // ── Input handling ──

  onTextInput(e, type, key) {
    if (type === 'arg') this.setArg(key, e.target.value);
    else this.setProp(key, e.target.value);
    this.checkAutocomplete(e, type, key);
  },

  onInputKeydown(e, type, key, step) {
    if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;
    const val = e.target.value;
    if (/[$a-zA-Z_]/.test(val)) return;
    const num = parseFloat(val);
    if (isNaN(num)) return;
    e.preventDefault();
    const s = step || 1;
    const mult = e.shiftKey ? 10 : 1;
    const raw = e.key === 'ArrowUp' ? num + s * mult : num - s * mult;
    const decimals = s < 1 ? (String(s).split('.')[1] || '').length : 0;
    const result = decimals > 0 ? raw.toFixed(decimals) : String(Math.round(raw * 100) / 100);
    e.target.value = result;
    if (type === 'arg') this.setArg(key, result);
    else this.setProp(key, result);
  },

  // ── Resolved values (for property editor pills) ──

  resolvedValue(expr) {
    if (!expr) return '';
    const s = String(expr);
    if (!s.includes('$') && !/[a-z]{2,}\(/.test(s)) return '';
    const rctx = this.resolvedRenderCtx;
    const f = tryResolveFloat(s, rctx);
    if (!isNaN(f)) {
      return f === Math.floor(f) ? String(Math.floor(f)) : f.toFixed(2);
    }
    try {
      const text = resolveText(s, rctx);
      if (text !== s && text !== s.replace(/^"|"$/g, '')) return text;
    } catch(e) {}
    try { return resolveColor(s, rctx); } catch(e) {}
    return '';
  },

  resolvedVarValue(name) {
    const rctx = this.resolvedRenderCtx;
    if (!rctx._customVars || rctx._customVars[name] == null) return '';
    const val = rctx._customVars[name];
    const num = parseFloat(val);
    if (!isNaN(num) && String(num) === val) {
      return num === Math.floor(num) ? String(Math.floor(num)) : num.toFixed(2);
    }
    return val;
  },

  fmtNum(v) {
    if (v == null) return '--';
    if (v === Math.floor(v)) return String(Math.floor(v));
    return v.toFixed(1);
  }
};
