export class Control {
  constructor(offsetX, offsetY, width, height, label = "", readOnly = false, hidden = false) {
    this.offsetX = offsetX;
    this.offsetY = offsetY;
    this.width = width;
    this.height = height;

    this.label = label;
    this.isHovered = false;
    this.isFocused = false;

    this.value = null;
    this.onChange = null;
    this.readOnly = readOnly;
    this.hidden = hidden;

    this.dragStartX = 0;
    this.dragAccum = 0;
    this.dragging = false;

  }

  getValue() {
    return this.value;
  }

  draw(ctx, parentX, parentY) {
    const x = parentX + this.offsetX;
    const y = parentY + this.offsetY;

    ctx.save();
    ctx.fillStyle = this.isHovered ? "#eee" : "#f8f8f8";
    ctx.strokeStyle = this.isFocused ? "#0af" : "#999";
    ctx.lineWidth = 1;

    ctx.beginPath();
    ctx.roundRect(x, y, this.width, this.height, 6);
    ctx.fill();
    ctx.stroke();

    this.drawLabel(ctx, x, y);

    ctx.restore();
  }

  drawLate(ctx, parentX, parentY) {

  }

  drawLabel(ctx, x, y) {
    ctx.fillStyle = "#333";
    ctx.font = "12px sans-serif";
    ctx.textAlign = "left";
    ctx.textBaseline = "top";
    ctx.fillText(this.label, x + 6, y + 6);
  }

  contains(mx, my, parentX, parentY) {
    const x = parentX + this.offsetX;
    const y = parentY + this.offsetY;
    return mx > x && mx < x + this.width &&
      my > y && my < y + this.height;
  }

  onEvent(type, data) {
    if (this.readOnly) {
      if (type === "mouseMove") {
        return this.onMouseMove?.(data.mx, data.my, data.parentX, data.parentY, data.modifiers);
      }
      return;
    }
    switch (type) {
      case "mouseDown": return this.onMouseDown?.(data.mx, data.my, data.parentX, data.parentY);
      case "mouseUp": return this.onMouseUp?.(data.mx, data.my, data.parentX, data.parentY);
      case "mouseMove": return this.onMouseMove?.(data.mx, data.my, data.parentX, data.parentY, data.modifiers);
      case "keyDown": return this.onKeyDown?.(data.key);
      case "scroll": return this.onScroll?.(data.delta);
      case "clipboard": return this.onClipboard?.(data.event);
      default:
        console.warn(`Unhandled event type: ${type}`, data);

    }
  }


  onMouseDown(mx, my, parentX, parentY) {
    const x = parentX + this.offsetX;
    const y = parentY + this.offsetY;

    if (mx > x && mx < x + this.width &&
      my > y && my < y + this.height) {
      this.onClick(); // delegate to subclass
      return true;
    }
    return false;
  }


  onMouseUp(mx, my, parentX, parentY) {
    const x = parentX + this.offsetX;
    const y = parentY + this.offsetY;

    const inside = mx > x && mx < x + this.width &&
      my > y && my < y + this.height;

    this.dragging = false;

    if (!inside) {
      this.onBlur();
    }
  }


  onMouseMove(mx, my, parentX, parentY, modifiers = {}) {
    if (!this.dragging) return;

    const deltaX = mx - this.dragStartX;
    this.dragStartX = mx;
    this.dragAccum += deltaX;

    this.onDragDelta?.(deltaX, modifiers);
  }


  onScroll(delta) {
    const step = 1;
    this.value = Math.max(this.min, Math.min(this.max, this.value + delta * step));
    if (this.onChange) this.onChange(this.value);
  }


  onHover(state) {
    this.isHovered = state;
  }

  onFocus(state) {
    this.isFocused = state;
  }

  onClick() {
    this.onFocus(true);
  }

  onBlur() {
    this.onFocus(false);
  }

  onClipboard(e) {
    // Default: do nothing
  }

}

export class SliderControl extends Control {
  constructor(x, y, width, height, label = "", min = 0, max = 100, initial = 50, readOnly = false) {
    super(x, y, width, height, label, readOnly);
    this.min = min;
    this.max = max;
    this.value = initial;
  }

  getValue() {
    return this.value;
  }

  draw(ctx, parentX, parentY) {
    const x = parentX + this.offsetX;
    const y = parentY + this.offsetY;

    // Draw background and border
    ctx.save();
    ctx.fillStyle = this.isHovered ? "#eee" : "#f8f8f8";
    ctx.strokeStyle = this.isFocused ? "#0af" : "#999";
    ctx.lineWidth = 1;

    ctx.beginPath();
    ctx.roundRect(x, y, this.width, this.height, 6);
    ctx.fill();
    ctx.stroke();

    this.drawLabel(ctx, x, y);

    // Draw value (top-right)
    ctx.textAlign = "right";
    ctx.fillText(this.value.toFixed(0), x + this.width - 6, y + 6);

    // Draw slider track
    const padding = 10;
    const trackY = y + this.height - 14; // lower in control
    const trackStart = x + padding;
    const trackEnd = x + this.width - padding;
    const trackWidth = trackEnd - trackStart;

    const percent = (this.value - this.min) / (this.max - this.min);
    const knobX = trackStart + percent * trackWidth;

    ctx.strokeStyle = "#aaa";
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(trackStart, trackY);
    ctx.lineTo(trackEnd, trackY);
    ctx.stroke();

    // Draw knob
    ctx.fillStyle = this.isFocused ? "#0af" : "#666";
    ctx.beginPath();
    ctx.arc(knobX, trackY, 6, 0, Math.PI * 2);
    ctx.fill();

    ctx.restore();
  }



  onMouseDown(mx, my, parentX, parentY) {
    const handled = super.onMouseDown(mx, my, parentX, parentY);
    if (handled) {
      this.dragging = true;
      this.dragStartX = mx;
      this.dragAccum = 0;
      return true;
    }
    return false;
  }


  onMouseUp(mx, my, parentX, parentY) {
    this.dragging = false;
    this.onBlur();
  }

  onScroll(delta) {
    super.onScroll?.(delta); // optional chaining in case base is extended later
  }

  onDragDelta(accumulatedDelta, modifiers = {}) {
    const trackWidth = this.width - 20;
    const valuePerPixel = (this.max - this.min) / trackWidth;

    const screenDelta = accumulatedDelta / (1 / modifiers.zoom || 1);
    const sensitivity = modifiers.ctrl ? 0.015 : 1.0;

    const deltaValue = screenDelta * valuePerPixel * sensitivity;

    this.dragAccum += deltaValue;

    if (Math.abs(this.dragAccum) >= 1) {
      const change = Math.round(this.dragAccum);
      this.value = Math.max(this.min, Math.min(this.max, this.value + change));
      this.dragAccum -= change;
      if (this.onChange) this.onChange(this.value);
    }
  }

}


export class TextInputControl extends Control {
  constructor(x, y, width, height, inputType = "text", label = "", initial = "", readOnly = false) {
    super(x, y, width, height, label, readOnly);
    this.text = String(initial);
    this.cursorVisible = false;
    this.cursorTimer = 0;

    this.selecting = false;
    this.selectionStart = 0;
    this.selectionEnd = 0;
    this.cursorIndex = this.text.length;

    this.inputType = inputType;//"float"; // "text", "int", "float", "password"
    this.allowNegative = true;
    this.maxLength = 20;
    this.regex = /^[a-zA-Z0-9]+$/

  }

  getValue() {
    return this.text;
  }

  draw(ctx, parentX, parentY) {
    const x = parentX + this.offsetX;
    const y = parentY + this.offsetY;

    ctx.save();
    ctx.fillStyle = this.isHovered ? "#eee" : "#fff";
    ctx.strokeStyle = this.isFocused ? "#0af" : "#999";
    ctx.lineWidth = 1;

    ctx.beginPath();
    ctx.roundRect(x, y, this.width, this.height, 6);
    ctx.fill();
    ctx.stroke();

    this.drawLabel(ctx, x, y);

    const textBefore = this.text.slice(0, this.selectionStart);
    const selectedText = this.text.slice(this.selectionStart, this.selectionEnd);

    const textWidthBefore = ctx.measureText(textBefore).width;
    const selectedWidth = ctx.measureText(selectedText).width;

    if (this.selectionStart !== this.selectionEnd) {
      ctx.fillStyle = "#cce5ff";
      ctx.fillRect(x + 6 + textWidthBefore, textY - 10, selectedWidth, 20);
    }


    // Text field
    ctx.font = "13px monospace";
    ctx.textBaseline = "middle";
    ctx.fillStyle = "#000";
    ctx.textAlign = "left";
    const textY = y + this.height / 2 + 4;
    const displayText = this.inputType === "password"
      ? "*".repeat(this.text.length)
      : this.text;
    ctx.fillText(displayText, x + 6, textY);


    // Blinking cursor
    if (this.isFocused && this.cursorVisible) {
      const textWidth = ctx.measureText(this.text).width;
      const cursorX = x + 6 + ctx.measureText(this.text.slice(0, this.cursorIndex)).width;
      ctx.beginPath();
      ctx.moveTo(cursorX, textY - 8);
      ctx.lineTo(cursorX, textY + 8);
      ctx.strokeStyle = "#0af";
      ctx.stroke();

    }

    ctx.restore();
  }

  onClick() {
    super.onClick();
    this.cursorVisible = true;
    this.cursorTimer = 0;
  }

  onScroll(delta) {
    const current = parseFloat(this.text) || 0;
    const step = this.inputType === "int" ? 1 : 0.1;
    const next = current + delta * step;

    // Clamp if min/max are defined
    const clamped = Math.max(this.min ?? -Infinity, Math.min(this.max ?? Infinity, next));

    // Round to avoid floating point artifacts
    const precision = step < 1 ? 2 : 0;
    const factor = Math.pow(10, precision);
    const rounded = Math.round(clamped * factor) / factor;

    this.text = String(rounded);
    this.cursorIndex = this.text.length;
    this.selectionStart = this.selectionEnd = this.cursorIndex;

    if (this.onChange) this.onChange(this.getValue());
  }




  onKeyDown(key) {
    if (!this.isFocused) return;

    const hasShift = window.event?.shiftKey;

    if (key === "ArrowLeft") {
      if (hasShift) {
        this.cursorIndex = Math.max(0, this.cursorIndex - 1);
        this.selectionEnd = this.cursorIndex;
      } else {
        this.cursorIndex = Math.max(0, this.cursorIndex - 1);
        this.selectionStart = this.selectionEnd = this.cursorIndex;
      }
    } else if (key === "ArrowRight") {
      if (hasShift) {
        this.cursorIndex = Math.min(this.text.length, this.cursorIndex + 1);
        this.selectionEnd = this.cursorIndex;
      } else {
        this.cursorIndex = Math.min(this.text.length, this.cursorIndex + 1);
        this.selectionStart = this.selectionEnd = this.cursorIndex;
      }

    } else if (key === "Backspace") {
      if (this.selectionStart !== this.selectionEnd) {
        this.text = this.text.slice(0, this.selectionStart) + this.text.slice(this.selectionEnd);
        this.cursorIndex = this.selectionStart;
      } else if (this.cursorIndex > 0) {
        this.text = this.text.slice(0, this.cursorIndex - 1) + this.text.slice(this.cursorIndex);
        this.cursorIndex--;
      }
      this.selectionStart = this.selectionEnd = this.cursorIndex;
    } else if (key === "Delete") {
      if (this.selectionStart !== this.selectionEnd) {
        this.text = this.text.slice(0, this.selectionStart) + this.text.slice(this.selectionEnd);
        this.cursorIndex = this.selectionStart;
      } else if (this.cursorIndex < this.text.length) {
        this.text = this.text.slice(0, this.cursorIndex) + this.text.slice(this.cursorIndex + 1);
      }
      this.selectionStart = this.selectionEnd = this.cursorIndex;



    } else if (key.length === 1) {
      const preview = this.text.slice(0, this.selectionStart) + key + this.text.slice(this.selectionEnd);
      if (!this.isValidInput(preview)) return;

      if (this.selectionStart !== this.selectionEnd) {
        this.text = this.text.slice(0, this.selectionStart) + key + this.text.slice(this.selectionEnd);
        this.cursorIndex = this.selectionStart + 1;
      } else {
        this.text = this.text.slice(0, this.cursorIndex) + key + this.text.slice(this.cursorIndex);
        this.cursorIndex++;
      }

      this.selectionStart = this.selectionEnd = this.cursorIndex;
    }



    if (this.onChange) this.onChange(this.text);
  }

  onClipboard(e) {
    if (e.ctrlKey && e.key === "c") {
      const selected = this.text.slice(this.selectionStart, this.selectionEnd);
      navigator.clipboard.writeText(selected);
      e.preventDefault();
    }

    if (e.ctrlKey && e.key === "v") {
      navigator.clipboard.readText().then((clip) => {
        let filtered = clip;

        const preview = this.text.slice(0, this.selectionStart) + clip + this.text.slice(this.selectionEnd);
        if (!this.isValidInput(preview)) return;



        const before = this.text.slice(0, this.selectionStart);
        const after = this.text.slice(this.selectionEnd);
        this.text = before + filtered + after;
        this.cursorIndex = before.length + filtered.length;
        this.selectionStart = this.selectionEnd = this.cursorIndex;
        if (this.onChange) this.onChange(this.text);
      });

      e.preventDefault();
    }
  }

  isValidInput(input, strict = false) {
    // Allow empty input
    if (input === "") return true;

    if (this.inputType === "int") {
      if (!strict) {
        // Allow "-" while typing
        if (input === "-" && this.allowNegative) return true;
      }
      const intPattern = this.allowNegative ? /^-?\d+$/ : /^\d+$/;
      return intPattern.test(input);
    }

    if (this.inputType === "float") {
      if (!strict) {
        // Relaxed pattern for typing
        const relaxedPattern = this.allowNegative
          ? /^-?(\d+)?(\.)?(\d*)?$/
          : /^(\d+)?(\.)?(\d*)?$/;
        return relaxedPattern.test(input);
      }

      // Auto-correct ".123" → "0.123", "-.123" → "-0.123"
      if (/^\.\d+$/.test(input)) {
        this.text = "0" + input;
      } else if (/^-\.\d+$/.test(input)) {
        input = "-0" + input.slice(1);
        console.log(">");
      }

      // Strict pattern for finalized float
      const strictPattern = this.allowNegative
        ? /^-?\d+(\.\d+)?$/
        : /^\d+(\.\d+)?$/;

      return strictPattern.test(input);
    }


    // Regex check (applies to all types)
    if (this.regex && !this.regex.test(input)) return false;

    // Max length
    const currentLength = this.text.length - (this.selectionEnd - this.selectionStart);
    if (input.length + currentLength > this.maxLength) return false;

    return true;
  }



  onBlur() {
    super.onBlur();
    if (!this.isValidInput(this.text, true)) {
      // Optionally reset or trim invalid input
      // this.text = "";
      // this.cursorIndex = 0;
      // this.selectionStart = this.selectionEnd = 0;
      if (this.onChange) this.onChange(this.text);
    }
  }




  update(deltaTime) {
    if (this.isFocused) {
      this.cursorTimer += deltaTime;
      if (this.cursorTimer > 500) {
        this.cursorVisible = !this.cursorVisible;
        this.cursorTimer = 0;
      }
    }
  }
}

export class DropdownControl extends Control {
  constructor(x, y, width, height, label = "", options = [], initial = "", readOnly = false) {
    super(x, y, width, height, label, readOnly);
    this.options = options;
    if (typeof initial === "number" && initial >= 0 && initial < options.length) {
      this.value = options[initial];
    } else if (typeof initial === "string" && options.includes(initial)) {
      this.value = initial;
    } else {
      this.value = options.length > 0 ? options[0] : "";
    }

    this.isOpen = false;
    this.hoveredIndex = -1;

  }

  getValue() {
    return this.options.indexOf(this.value);
    //return this.value; // returns string vlue
  }

  draw(ctx, parentX, parentY) {
    const x = parentX + this.offsetX;
    const y = parentY + this.offsetY;

    ctx.save();
    ctx.fillStyle = this.isHovered ? "#eee" : "#f8f8f8";
    ctx.strokeStyle = this.isFocused ? "#0af" : "#999";
    ctx.lineWidth = 1;

    ctx.beginPath();
    ctx.roundRect(x, y, this.width, this.height, 6);
    ctx.fill();
    ctx.stroke();

    this.drawLabel(ctx, x, y);

    // Draw selected value
    ctx.fillStyle = "#000";
    ctx.font = "13px sans-serif";
    ctx.textAlign = "right";
    ctx.textBaseline = "middle";
    ctx.fillText(this.value, x + this.width - 20, y + this.height / 2);

    // Draw dropdown arrow
    ctx.beginPath();
    ctx.moveTo(x + this.width - 16, y + this.height / 2 - 4);
    ctx.lineTo(x + this.width - 10, y + this.height / 2 - 4);
    ctx.lineTo(x + this.width - 13, y + this.height / 2 + 2);
    ctx.closePath();
    ctx.fillStyle = "#666";
    ctx.fill();



    ctx.restore();

  }

  drawLate(ctx, parentX, parentY) {
    const x = parentX + this.offsetX;
    const y = parentY + this.offsetY;
    // Draw options if open
    if (this.isOpen) {
      for (let i = 0; i < this.options.length; i++) {
        const optY = y + this.height + i * this.height;
        const isHovered = i === this.hoveredIndex;

        ctx.fillStyle = isHovered ? "#cce5ff" : (this.options[i] === this.value ? "#ddd" : "#fff");
        ctx.strokeStyle = "#999";
        ctx.beginPath();
        ctx.rect(x, optY, this.width, this.height);
        ctx.fill();
        ctx.stroke();

        ctx.fillStyle = "#000";
        ctx.textAlign = "left";
        ctx.fillText(this.options[i], x + 6, optY + this.height / 2);
      }
    }
  }

  onClick() {
    super.onClick();
    this.isOpen = !this.isOpen;
  }

  onMouseDown(mx, my, parentX, parentY) {
    const x = parentX + this.offsetX;
    const y = parentY + this.offsetY;

    if (this.isOpen) {
      for (let i = 0; i < this.options.length; i++) {
        const optY = y + this.height + i * this.height;
        if (mx > x && mx < x + this.width && my > optY && my < optY + this.height) {
          this.value = this.options[i];
          this.isOpen = false;
          if (this.onChange) this.onChange(this.value);
          return true;
        }
      }
    }
    return super.onMouseDown(mx, my, parentX, parentY);
  }

  onMouseMove(mx, my, parentX, parentY) {
    if (!this.isOpen) {
      this.hoveredIndex = -1;
      return;
    }

    const x = parentX + this.offsetX;
    const y = parentY + this.offsetY;

    this.hoveredIndex = -1;

    for (let i = 0; i < this.options.length; i++) {
      const optY = y + this.height + i * this.height;
      if (mx > x && mx < x + this.width && my > optY && my < optY + this.height) {
        this.hoveredIndex = i;
        break;
      }
    }
  }

  onScroll(delta) {
    delta = -delta;
    const currentIndex = this.options.indexOf(this.value);
    if (currentIndex === -1 || this.options.length === 0) return;

    const nextIndex = Math.max(0, Math.min(this.options.length - 1, currentIndex + delta));
    this.value = this.options[nextIndex];

    if (this.onChange) this.onChange(this.getValue());
  }

  onBlur() {
    super.onBlur();
    this.isOpen = false;
  }
}
