<template>
  <div class="cw-composer-layout">
    <!-- Left panel (settings or selectors) -->
    <div class="cw-left-panel">
      <FormBlockSettings v-if="selectedBlockId" />
      <FormBlockSelector v-else />
    </div>

    <!-- Center Canvas Area -->
    <div class="cw-canvas-pane">
      <FormEditorToolbar />
      <div
        class="cw-canvas-wrap"
        :style="canvasWrapStyle"
        @click.self="selectedBlockId = null"
      >
        <div
          class="cw-form-frame"
          :style="formFrameStyle"
          :class="{ 'is-dragging': isDragging, 'is-mobile': previewDevice === 'mobile' }"
        >
          <FormCanvas v-model="localBlocks" />
        </div>
      </div>
    </div>

    <!-- HTML Expand Modal -->
    <div v-if="showHtmlEditor" class="modal-overlay is-open" @click.self="showHtmlEditor = false">
      <div class="modal" style="max-width: 900px; width: 100%; height: 80vh; display: flex; flex-direction: column;">
        <div class="modal-header">
          <h3 class="modal-title">HTML Code Editor</h3>
          <button type="button" class="modal-close" @click="showHtmlEditor = false">✕</button>
        </div>
        <div class="modal-body" style="flex: 1; min-height: 0; padding: 0; position: relative; overflow: hidden;">
          <div class="cw-code-editor" style="height: 100%; border-radius: 0; border: none;">
            <div class="cw-code-lines" ref="modalCodeLineRef" aria-hidden="true" style="padding-top:16px;">
              <span v-for="n in htmlLineCount(htmlEditorBlock?.html)" :key="n">{{ n }}</span>
            </div>
            <textarea
              class="cw-code-textarea"
              v-model="htmlEditorBlock.html"
              spellcheck="false"
              placeholder="<!-- Raw HTML code here -->"
              @scroll="syncModalCodeScroll"
              ref="modalCodeEditorRef"
              style="padding-top:16px;"
            ></textarea>
          </div>
        </div>
        <div class="modal-footer" style="padding: 12px 16px; display: flex; justify-content: flex-end; gap: 8px; border-top: 1px solid #e5e7eb;">
          <button type="button" class="btn btn-ghost btn-sm" @click="showHtmlEditor = false">Close</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, provide, nextTick, onMounted, onUnmounted, watch } from 'vue';
import FormBlockSelector from './FormBlockSelector.vue';
import FormBlockSettings from './FormBlockSettings.vue';
import FormCanvas from './FormCanvas.vue';
import FormEditorToolbar from './FormEditorToolbar.vue';
import { makeBlock, ensureBlockIds, isLegacyFieldShape } from './formEditorUtils';

const props = defineProps({
  modelValue: { type: Array, required: true },
});

const emit = defineEmits(['update:modelValue']);

const localBlocks = ref([]);
const selectedBlockId = ref(null);
const isDragging = ref(false);
const previewDevice = ref('desktop');

const historyStack = ref([]);
const historyIndex = ref(-1);
let isUndoingOrRedoing = false;

function onDragStart() { isDragging.value = true; }
function onDragEnd() { isDragging.value = false; saveHistory(); }

function saveHistory() {
  if (isUndoingOrRedoing) return;
  const stateStr = JSON.stringify(localBlocks.value);
  if (historyIndex.value >= 0 && historyStack.value[historyIndex.value] === stateStr) return;
  if (historyIndex.value < historyStack.value.length - 1) {
    historyStack.value = historyStack.value.slice(0, historyIndex.value + 1);
  }
  historyStack.value.push(stateStr);
  if (historyStack.value.length > 50) historyStack.value.shift();
  historyIndex.value = historyStack.value.length - 1;
  emit('update:modelValue', JSON.parse(stateStr));
}

const canUndo = computed(() => historyIndex.value > 0);
const canRedo = computed(() => historyIndex.value < historyStack.value.length - 1);

// Settings-panel inputs (color pickers, padding/margin numbers, label/help
// text, selects, the Design tab, etc.) mutate localBlocks reactively but
// don't each call saveHistory() individually - this deep watcher is the
// single place that guarantees every edit eventually reaches the parent's
// v-model (and therefore gets included next time the page is saved),
// regardless of which input changed it. Debounced so continuous typing/
// dragging doesn't spam the undo stack with one entry per keystroke.
let flushTimer = null;
watch(localBlocks, () => {
  clearTimeout(flushTimer);
  flushTimer = setTimeout(saveHistory, 400);
}, { deep: true });

function undo() {
  if (historyIndex.value > 0) {
    isUndoingOrRedoing = true;
    historyIndex.value--;
    localBlocks.value = JSON.parse(historyStack.value[historyIndex.value]);
    selectedBlockId.value = null;
    nextTick(() => { isUndoingOrRedoing = false; });
  }
}

function redo() {
  if (historyIndex.value < historyStack.value.length - 1) {
    isUndoingOrRedoing = true;
    historyIndex.value++;
    localBlocks.value = JSON.parse(historyStack.value[historyIndex.value]);
    selectedBlockId.value = null;
    nextTick(() => { isUndoingOrRedoing = false; });
  }
}

function handleKeyDown(e) {
  if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
    e.preventDefault();
    if (e.shiftKey) redo(); else undo();
  } else if ((e.ctrlKey || e.metaKey) && e.key === 'y') {
    e.preventDefault();
    redo();
  }
}

function defaultTemplate() {
  return [
    makeBlock({ type: 'heading' }),
    makeBlock({ type: 'text' }),
    makeBlock({ type: 'field-email' }),
    makeBlock({ type: 'submit-button' }),
  ];
}

function ensureBodyBlock() {
  const hasBody = localBlocks.value.some((b) => b.type === 'body');
  if (!hasBody) {
    localBlocks.value.unshift({
      id: 'form-body-settings',
      type: 'body',
      bgColor: '#ffffff',
      contentWidth: 480,
      borderRadius: 12,
      fontFamily: 'Inter',
    });
  }
}

onMounted(() => {
  if (props.modelValue.length === 0 || isLegacyFieldShape(props.modelValue)) {
    localBlocks.value = defaultTemplate();
  } else {
    localBlocks.value = props.modelValue.map((b) => ensureBlockIds(b));
  }
  ensureBodyBlock();
  historyStack.value = [JSON.stringify(localBlocks.value)];
  historyIndex.value = 0;
  emit('update:modelValue', localBlocks.value);
  window.addEventListener('keydown', handleKeyDown);
});

onUnmounted(() => {
  clearTimeout(flushTimer);
  window.removeEventListener('keydown', handleKeyDown);
});

function findBlockById(id) {
  for (const b of localBlocks.value) {
    if (b.id === id) return b;
    if (b.type === 'column') {
      for (const col of b.columns) {
        const found = col.blocks.find((nb) => nb.id === id);
        if (found) return found;
      }
    }
  }
  return null;
}

const selectedBlock = computed(() => findBlockById(selectedBlockId.value));

const bodyBlock = computed(() => {
  return localBlocks.value.find((b) => b.type === 'body') || { bgColor: '#ffffff', contentWidth: 480, borderRadius: 12 };
});

const canvasWrapStyle = computed(() => ({ background: 'var(--bg-page)' }));

const formFrameStyle = computed(() => {
  const configuredWidth = bodyBlock.value.contentWidth || 480;
  const width = previewDevice.value === 'mobile' ? Math.min(configuredWidth, 375) : configuredWidth;

  return {
    backgroundColor: bodyBlock.value.bgColor || '#ffffff',
    maxWidth: `${width}px`,
    borderRadius: (bodyBlock.value.borderRadius ?? 12) + 'px',
    fontFamily: bodyBlock.value.fontFamily ? `'${bodyBlock.value.fontFamily}', sans-serif` : undefined,
  };
});

function selectBlock(id) { selectedBlockId.value = id; }

function duplicateBlock(id) {
  const block = findBlockById(id);
  if (block && block.type === 'submit-button') return;

  function cloneBlock(b) {
    const cloned = JSON.parse(JSON.stringify(b));
    cloned.id = crypto.randomUUID();
    if (cloned.type === 'column' && cloned.columns) {
      cloned.columns = cloned.columns.map((col) => ({
        ...col,
        id: crypto.randomUUID(),
        blocks: (col.blocks || []).map(cloneBlock),
      }));
    }
    return cloned;
  }

  const idx = localBlocks.value.findIndex((b) => b.id === id);
  if (idx >= 0) {
    localBlocks.value.splice(idx + 1, 0, cloneBlock(localBlocks.value[idx]));
    saveHistory();
    return;
  }
  for (const b of localBlocks.value) {
    if (b.type === 'column' && b.columns) {
      for (const col of b.columns) {
        const ni = col.blocks.findIndex((nb) => nb.id === id);
        if (ni >= 0) {
          col.blocks.splice(ni + 1, 0, cloneBlock(col.blocks[ni]));
          saveHistory();
          return;
        }
      }
    }
  }
}

function deleteBlock(id) {
  const idx = localBlocks.value.findIndex((b) => b.id === id);
  if (idx >= 0) {
    localBlocks.value.splice(idx, 1);
  } else {
    for (const b of localBlocks.value) {
      if (b.type === 'column') {
        for (const col of b.columns) {
          const ni = col.blocks.findIndex((nb) => nb.id === id);
          if (ni >= 0) { col.blocks.splice(ni, 1); break; }
        }
      }
    }
  }
  if (selectedBlockId.value === id) selectedBlockId.value = null;
  saveHistory();
}

function updateContent(id, value) {
  const block = findBlockById(id);
  if (block) block.content = value;
}

function onColumnBlockAdded(col) {
  if (col.blocks.length > 0) selectedBlockId.value = col.blocks[col.blocks.length - 1].id;
  saveHistory();
}

function onBlockAdded() {
  if (localBlocks.value.length > 0) {
    const last = localBlocks.value[localBlocks.value.length - 1];
    selectedBlockId.value = last.id;
  }
  saveHistory();
}

// HTML editor expand modal state
const showHtmlEditor = ref(false);
const htmlEditorBlock = ref(null);
const modalCodeLineRef = ref(null);
const modalCodeEditorRef = ref(null);

function openHtmlEditor(block) {
  htmlEditorBlock.value = block;
  showHtmlEditor.value = true;
}

function htmlLineCount(html) {
  const lines = (html || '').split('\n').length;
  return Array.from({ length: Math.max(lines, 1) }, (_, i) => i + 1);
}

function syncModalCodeScroll(e) {
  if (modalCodeLineRef.value) modalCodeLineRef.value.scrollTop = e.target.scrollTop;
}

// Lets the parent (Pages/Forms/Builder.vue) force any pending debounced
// edit to flush into the v-model synchronously before it reads the blocks
// for its own save payload - otherwise a Save click within the 400ms
// debounce window could send stale data.
function flush() {
  clearTimeout(flushTimer);
  saveHistory();
}
defineExpose({ flush });

provide('formEditorState', {
  blocks: localBlocks,
  selectedBlockId,
  selectedBlock,
  bodyBlock,
  isDragging,
  previewDevice,
  showHtmlEditor,
  htmlEditorBlock,
  canUndo,
  canRedo,
  selectBlock,
  duplicateBlock,
  deleteBlock,
  updateContent,
  onColumnBlockAdded,
  onBlockAdded,
  onDragStart,
  onDragEnd,
  undo,
  redo,
  saveHistory,
  makeBlock,
  openHtmlEditor,
});
</script>

<style scoped>
.cw-composer-layout {
    display: flex;
    height: 100%;
    width: 100%;
    overflow: hidden;
    background: var(--bg-surface);
    min-height: 560px;

    @media (max-width: 1024px) {
        flex-direction: column;
        overflow-y: auto;
        height: auto;
    }
}

.cw-left-panel {
    width: 300px;
    flex-shrink: 0;
    background: var(--bg-surface);
    border-right: 1px solid var(--border-color);
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;

    @media (max-width: 1024px) {
        width: 100%;
        border-right: none;
        border-bottom: 1px solid var(--border-color);
        max-height: 380px;
        overflow-y: auto;
    }
}

.cw-canvas-pane {
    flex: 1;
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
    min-width: 0;

    @media (max-width: 1024px) {
        height: auto;
        overflow: visible;
    }
}

.cw-canvas-wrap {
    flex: 1;
    overflow-y: auto;
    background: var(--bg-page);
    padding: 36px 56px;
    display: flex;
    justify-content: center;
    align-items: flex-start;

    @media (max-width: 768px) {
        padding: 16px 12px;
    }
}

.cw-form-frame {
    width: 100%;
    max-width: 480px;
    background: #ffffff;
    min-height: 200px;
    box-shadow: 0 2px 16px rgba(0,0,0,0.12);
    transition: max-width 0.25s ease;
}

</style>
