<script setup>
import { computed, defineAsyncComponent, ref, useAttrs } from "vue";
import { useQuasar } from "quasar";
import { sanitizeHtml } from "../../composables/sanitizeHtml";
const $q = useQuasar();
const editorRef = ref(null);
const textColorRef = ref(null);
const textHightlightRef = ref(null);
const insertTableRef = ref(null);
const fontSizeRef = ref(null);
const fontSizes = ["8px", "10px", "12px", "14px", "16px", "18px", "20px", "24px", "28px", "32px", "36px", "48px", "72px"];
const setFontSizeCMD = (size) => {
  const edit = editorRef.value;
  fontSizeRef.value.hide();
  edit.caret.restore();
  document.execCommand("fontSize", false, "7");
  const contentEl = edit.$el.querySelector(".q-editor__content");
  contentEl.querySelectorAll('font[size="7"]').forEach((el) => {
    const span = document.createElement("span");
    span.style.fontSize = size;
    span.innerHTML = el.innerHTML;
    el.parentNode.replaceChild(span, el);
  });
  emit("update:modelValue", contentEl.innerHTML);
  edit.focus();
};
const highlight = ref("#ffff00aa");
const foreColor = ref("#000000");
const tableRows = ref(3);
const tableCols = ref(3);
const onEditorBlur = () => {
  emit("update:modelValue", sanitizeHtml(props.modelValue));
};
const TextColorCMD = ((cmd, name) => {
  const edit = editorRef.value;
  textColorRef.value.hide();
  edit.caret.restore();
  edit.runCmd(cmd, name);
  edit.focus();
});
const TextHightlightCMD = ((cmd, name) => {
  const edit = editorRef.value;
  textHightlightRef.value.hide();
  edit.caret.restore();
  edit.runCmd(cmd, name);
  edit.focus();
});
const insertTableCMD = () => {
  const edit = editorRef.value;
  insertTableRef.value.hide();
  edit.caret.restore();
  let tableHTML = '<table border="1" style="border-collapse: collapse; width: 100%;">\n';
  for (let i = 0; i < tableRows.value; i++) {
    tableHTML += "  <tr>\n";
    for (let j = 0; j < tableCols.value; j++) {
      const tag = i === 0 ? "th" : "td";
      tableHTML += `    <${tag} style="padding: 8px; border: 1px solid #ddd;">${tag === "th" ? `Header ${j + 1}` : ""}</${tag}>
`;
    }
    tableHTML += "  </tr>\n";
  }
  tableHTML += "</table>";
  edit.runCmd("insertHTML", tableHTML);
  edit.focus();
};
const toAbsoluteUrl = (value) => {
  const url = value?.trim();
  if (!url) return "";
  if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//") || url.startsWith("data:") || url.startsWith("blob:")) {
    return url;
  }
  return url.startsWith("/") ? `${window.location.origin}${url}` : `${window.location.origin}/${url}`;
};
const buildLinkHtml = (payload) => {
  const anchor = document.createElement("a");
  anchor.href = payload.url.trim();
  anchor.textContent = payload.text?.trim() || payload.url.trim();
  if (payload.title?.trim()) anchor.title = payload.title.trim();
  if (payload.target === "_blank") {
    anchor.target = "_blank";
    anchor.rel = "noopener noreferrer";
  }
  return anchor.outerHTML;
};
const buildImageHtml = (payload) => {
  const image = document.createElement("img");
  image.setAttribute("src", payload.source.trim());
  if (payload.alt?.trim()) image.setAttribute("alt", payload.alt.trim());
  const width = Number(payload.width);
  if (Number.isFinite(width) && width > 0) {
    image.setAttribute("width", String(Math.round(width)));
  }
  const height = Number(payload.height);
  if (Number.isFinite(height) && height > 0) {
    image.setAttribute("height", String(Math.round(height)));
  }
  return image.outerHTML;
};
const selectedImageNode = ref(null);
const selectedLinkNode = ref(null);
const updateSelectionState = (e) => {
  let target = e?.target;
  if (target instanceof HTMLImageElement) {
    selectedImageNode.value = target;
    selectedLinkNode.value = target.closest("a");
    return;
  }
  if (target && target.closest) {
    const anchor = target.closest("a");
    if (anchor) {
      selectedLinkNode.value = anchor;
      selectedImageNode.value = null;
      return;
    }
  }
  const selection = window.getSelection();
  if (!selection || !selection.rangeCount) {
    selectedLinkNode.value = null;
    selectedImageNode.value = null;
    return;
  }
  let node = selection.focusNode;
  if (!node) return;
  let el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
  selectedLinkNode.value = el?.closest("a");
  if (el instanceof HTMLImageElement) {
    selectedImageNode.value = el;
  } else if (el && selection.anchorNode === selection.focusNode) {
    const offset = selection.focusOffset;
    if (el.childNodes.length > offset) {
      const child = el.childNodes[offset];
      if (child instanceof HTMLImageElement) {
        selectedImageNode.value = child;
        return;
      }
    }
    const anchorOffset = selection.anchorOffset;
    if (el.childNodes.length > anchorOffset) {
      const child = el.childNodes[anchorOffset];
      if (child instanceof HTMLImageElement) {
        selectedImageNode.value = child;
        return;
      }
    }
    selectedImageNode.value = null;
  } else {
    selectedImageNode.value = null;
  }
};
const onEditorClick = (e) => {
  updateSelectionState(e);
};
let dialogOpening = false;
const onEditorDblClick = (e) => {
  e.stopPropagation();
  if (dialogOpening) return;
  updateSelectionState(e);
  if (selectedImageNode.value) {
    dialogOpening = true;
    insertEditImageCMD();
    setTimeout(() => {
      dialogOpening = false;
    }, 300);
  } else if (selectedLinkNode.value) {
    dialogOpening = true;
    insertEditLinkCMD();
    setTimeout(() => {
      dialogOpening = false;
    }, 300);
  }
};
const openInsertEditImageDialog = (initialSource = "", skipSaveCaret = false) => {
  const edit = editorRef.value;
  if (!skipSaveCaret) {
    edit.caret.save();
  }
  let initialAlt = "";
  let initialWidth = null;
  let initialHeight = null;
  if (selectedImageNode.value && !initialSource) {
    initialSource = selectedImageNode.value.getAttribute("src") || "";
    initialAlt = selectedImageNode.value.getAttribute("alt") || "";
    initialWidth = selectedImageNode.value.getAttribute("width") || selectedImageNode.value.clientWidth;
    initialHeight = selectedImageNode.value.getAttribute("height") || selectedImageNode.value.clientHeight;
  }
  $q.dialog({
    component: defineAsyncComponent(() => import("./DialogInsertEditImage.vue")),
    componentProps: { initialSource, initialAlt, initialWidth, initialHeight }
  }).onOk((payload) => {
    if (!payload?.source?.trim()) return;
    setTimeout(() => {
      edit.focus();
      edit.caret.restore();
      if (selectedImageNode.value && !skipSaveCaret) {
        selectedImageNode.value.setAttribute("src", payload.source.trim());
        if (payload.alt?.trim()) selectedImageNode.value.setAttribute("alt", payload.alt.trim());
        else selectedImageNode.value.removeAttribute("alt");
        if (payload.width) selectedImageNode.value.setAttribute("width", String(Math.round(payload.width)));
        else selectedImageNode.value.removeAttribute("width");
        if (payload.height) selectedImageNode.value.setAttribute("height", String(Math.round(payload.height)));
        else selectedImageNode.value.removeAttribute("height");
        emit("update:modelValue", edit.$el.querySelector(".q-editor__content").innerHTML);
      } else {
        edit.runCmd("insertHTML", buildImageHtml(payload));
      }
    }, 100);
  });
};
const insertEditLinkCMD = () => {
  const edit = editorRef.value;
  edit.caret.save();
  let initialUrl = "";
  let initialText = window.getSelection()?.toString() || "";
  let initialTitle = "";
  let initialTarget = "_self";
  if (selectedLinkNode.value) {
    initialUrl = selectedLinkNode.value.getAttribute("href") || "";
    initialText = selectedLinkNode.value.textContent || initialText;
    initialTitle = selectedLinkNode.value.getAttribute("title") || "";
    initialTarget = selectedLinkNode.value.getAttribute("target") === "_blank" ? "_blank" : "_self";
  }
  $q.dialog({
    component: defineAsyncComponent(() => import("./DialogInsertEditLink.vue")),
    componentProps: { initialUrl, initialText, initialTitle, initialTarget }
  }).onOk((payload) => {
    if (!payload?.url?.trim()) return;
    setTimeout(() => {
      edit.focus();
      edit.caret.restore();
      if (selectedLinkNode.value) {
        selectedLinkNode.value.setAttribute("href", payload.url.trim());
        selectedLinkNode.value.textContent = payload.text?.trim() || payload.url.trim();
        if (payload.title?.trim()) selectedLinkNode.value.setAttribute("title", payload.title.trim());
        else selectedLinkNode.value.removeAttribute("title");
        if (payload.target === "_blank") {
          selectedLinkNode.value.setAttribute("target", "_blank");
          selectedLinkNode.value.setAttribute("rel", "noopener noreferrer");
        } else {
          selectedLinkNode.value.removeAttribute("target");
          selectedLinkNode.value.removeAttribute("rel");
        }
        emit("update:modelValue", edit.$el.querySelector(".q-editor__content").innerHTML);
      } else {
        edit.runCmd("insertHTML", buildLinkHtml(payload));
      }
    }, 100);
  });
};
const insertEditImageCMD = () => {
  openInsertEditImageDialog();
};
const insertImageFromFileManagerCMD = () => {
  const edit = editorRef.value;
  edit.caret.save();
  $q.dialog({
    component: defineAsyncComponent(() => import("./DialogSelectFileFromFileManager.vue"))
  }).onOk((result) => {
    if (!result?.path?.trim()) return;
    let url = "";
    if (result.row && result.row.publicUrl) {
      url = result.row.publicUrl;
    } else {
      url = toAbsoluteUrl(result.path);
    }
    openInsertEditImageDialog(url, true);
  });
};
const emit = defineEmits(["update:modelValue"]);
const props = defineProps({
  fullscreen: { type: Boolean, required: false, skipCheck: true },
  noRouteFullscreenExit: { type: Boolean, required: false, skipCheck: true },
  modelValue: { type: String, required: true, default: "" },
  readonly: { type: Boolean, required: false, skipCheck: true },
  square: { type: Boolean, required: false, skipCheck: true },
  flat: { type: Boolean, required: false, skipCheck: true },
  dense: { type: Boolean, required: false, skipCheck: true, default: false },
  dark: { type: [Boolean, null], required: false, skipCheck: true },
  disable: { type: Boolean, required: false, skipCheck: true },
  minHeight: { type: null, required: false },
  maxHeight: { type: null, required: false },
  height: { type: null, required: false },
  definitions: { type: Object, required: false },
  fonts: { type: null, required: false, default: void 0 },
  toolbar: { type: null, required: false, default: void 0 },
  toolbarColor: { type: null, required: false },
  toolbarTextColor: { type: null, required: false },
  toolbarToggleColor: { type: null, required: false },
  toolbarBg: { type: null, required: false },
  toolbarOutline: { type: Boolean, required: false, skipCheck: true },
  toolbarPush: { type: Boolean, required: false, skipCheck: true },
  toolbarRounded: { type: Boolean, required: false, skipCheck: true },
  paragraphTag: { type: null, required: false },
  contentStyle: { type: null, required: false },
  contentClass: { type: null, required: false },
  placeholder: { type: null, required: false, default: "type something" },
  onFullscreen: { type: Function, required: false },
  "onUpdate:fullscreen": { type: Function, required: false },
  "onUpdate:modelValue": { type: Function, required: false },
  onDropdownShow: { type: Function, required: false },
  onDropdownBeforeShow: { type: Function, required: false },
  onDropdownHide: { type: Function, required: false },
  onDropdownBeforeHide: { type: Function, required: false },
  onLinkShow: { type: Function, required: false },
  onLinkHide: { type: Function, required: false }
});
const localValue = computed({
  get: () => props.modelValue,
  set: (value) => emit("update:modelValue", value)
});
const attrs = computed(() => {
  const a = { ...props };
  if (props.toolbar === void 0) a.toolbar = newToolBar;
  if (props.fonts === void 0) a.fonts = newFonts;
  a.definitions = {
    ...newDefinitions,
    ...props.definitions
  };
  return a;
});
const newToolBar = [
  [
    {
      label: $q.lang.editor.align,
      icon: $q.iconSet.editor.align,
      fixedLabel: true,
      list: "only-icons",
      options: ["left", "center", "right", "justify"]
    },
    {
      label: $q.lang.editor.align,
      icon: $q.iconSet.editor.align,
      fixedLabel: true,
      options: ["left", "center", "right", "justify"]
    }
  ],
  ["bold", "italic", "strike", "underline", "subscript", "superscript"],
  ["token", "hr", "insertEditLink", "insertEditImage", "insertImageFromFileManager"],
  //['print'],
  ["fullscreen"],
  [
    {
      label: $q.lang.editor.formatting,
      icon: $q.iconSet.editor.formatting,
      list: "no-icons",
      options: [
        "p",
        "h1",
        "h2",
        "h3",
        "h4",
        "h5",
        "h6",
        "code"
      ]
    },
    "fontSize",
    {
      label: $q.lang.editor.defaultFont,
      icon: $q.iconSet.editor.font,
      fixedIcon: true,
      list: "no-icons",
      options: [
        "default_font",
        "arial",
        "arial_black",
        "comic_sans",
        "courier_new",
        "impact",
        "lucida_grande",
        "times_new_roman",
        "verdana"
      ]
    },
    "textColor",
    "textHightlight",
    "insertTable",
    "custom_btn",
    "removeFormat"
  ],
  ["quote", "unordered", "ordered", "outdent", "indent"],
  ["undo", "redo"],
  ["viewsource"]
];
const newFonts = {
  arial: "Arial",
  arial_black: "Arial Black",
  comic_sans: "Comic Sans MS",
  courier_new: "Courier New",
  impact: "Impact",
  lucida_grande: "Lucida Grande",
  times_new_roman: "Times New Roman",
  verdana: "Verdana"
};
const newDefinitions = {
  insertEditLink: {
    tip: "Insert/Edit Link",
    icon: "sym_o_link",
    handler: insertEditLinkCMD
  },
  insertEditImage: {
    tip: "Insert/Edit Image",
    icon: "sym_o_add_photo_alternate",
    handler: insertEditImageCMD
  },
  insertImageFromFileManager: {
    tip: "Insert image from FileManager",
    icon: "sym_o_folder_open",
    handler: insertImageFromFileManagerCMD
  }
};
</script>

<template>
    <q-editor v-model="localValue" ref="editorRef" v-bind="attrs" @blur="onEditorBlur" @click="onEditorClick" @keyup="onEditorClick" @dblclick="onEditorDblClick">
                                <template v-slot:insertEditImage>
            <q-btn dense no-caps no-wrap
                :flat="!selectedImageNode"
                :unelevated="!!selectedImageNode"
                :color="selectedImageNode ? 'primary' : 'grey-8'" 
                icon="sym_o_add_photo_alternate" 
                size="sm" 
                @click="insertEditImageCMD">
                <q-tooltip>Insert/Edit Image</q-tooltip>
            </q-btn>
        </template>
        <template v-slot:insertEditLink>
            <q-btn dense no-caps no-wrap
                :flat="!selectedLinkNode"
                :unelevated="!!selectedLinkNode"
                :color="selectedLinkNode ? 'primary' : 'grey-8'" 
                icon="sym_o_link" 
                size="sm" 
                @click="insertEditLinkCMD">
                <q-tooltip>Insert/Edit Link</q-tooltip>
            </q-btn>
        </template>
        <template v-slot:fontSize>
            <q-btn-dropdown dense no-caps ref="fontSizeRef" no-wrap unelevated color="white" text-color="default"
                label="Font Size" icon="sym_o_format_size" size="sm">
                <q-list dense>
                    <q-item v-for="size in fontSizes" :key="size" clickable v-close-popup @click="setFontSizeCMD(size)">
                        <q-item-section>
                            <span :style="{ fontSize: size }">{{ size }}</span>
                        </q-item-section>
                    </q-item>
                </q-list>
            </q-btn-dropdown>
        </template>
        <template v-slot:textColor>
            <q-btn-dropdown dense no-caps ref="textColorRef" no-wrap unelevated color="white" text-color="default"
                label="Text Color" icon="sym_o_format_color_text" size="sm">
                <q-item tag="label" clickable @click="TextColorCMD('foreColor', foreColor)">
                    <q-item-section side>
                        <q-icon name="sym_o_format_color_text" />
                    </q-item-section>
                    <q-item-section>
                        <q-color v-model="foreColor" no-header no-footer style="max-width: 250px;" />
                    </q-item-section>
                </q-item>
            </q-btn-dropdown>
        </template>
        <template v-slot:textHightlight>
            <q-btn-dropdown dense no-caps ref="textHightlightRef" no-wrap unelevated color="white" text-color="default"
                label="Highlight" icon="sym_o_format_ink_highlighter" size="sm">

                <q-item tag="label" clickable @click="TextHightlightCMD('backColor', highlight)">
                    <q-item-section side>
                        <q-icon name="sym_o_format_ink_highlighter" />
                    </q-item-section>
                    <q-item-section>
                        <q-color v-model="highlight" no-header no-footer default-view="palette" :palette="[
  '#ffccccaa',
  '#ffe6ccaa',
  '#ffffccaa',
  '#ccffccaa',
  '#ccffe6aa',
  '#ccffffaa',
  '#cce6ffaa',
  '#ccccffaa',
  '#e6ccffaa',
  '#ffccffaa',
  '#ff0000aa',
  '#ff8000aa',
  '#ffff00aa',
  '#00ff00aa',
  '#00ff80aa',
  '#00ffffaa',
  '#0080ffaa',
  '#0000ffaa',
  '#8000ffaa',
  '#ff00ffaa'
]" style="max-width: 250px;" />
                    </q-item-section>
                </q-item>
            </q-btn-dropdown>
        </template>
        <template v-slot:insertTable>
            <q-btn-dropdown dense no-caps ref="insertTableRef" no-wrap unelevated color="white" text-color="default"
                label="Insert Table" icon="sym_o_table_chart" size="sm">
                <q-card style="min-width: 250px;">
                    <q-card-section>
                        <div class="text-subtitle2">Create HTML Table</div>
                    </q-card-section>
                    <q-card-section>
                        <div class="q-gutter-md">
                            <q-input
                                v-model.number="tableRows"
                                type="number"
                                label="Rows"
                                min="1"
                                max="20"
                                dense
                                outlined
                            />
                            <q-input
                                v-model.number="tableCols"
                                type="number"
                                label="Columns"
                                min="1"
                                max="10"
                                dense
                                outlined
                            />
                        </div>
                    </q-card-section>
                    <q-card-actions align="right">
                        <q-btn flat label="Cancel" color="grey" v-close-popup />
                        <q-btn flat label="Insert" color="primary" @click="insertTableCMD" />
                    </q-card-actions>
                </q-card>
            </q-btn-dropdown>
        </template>
    </q-editor>
</template>
