<template>
  <div class="json-view-demo">
    <section class="demo-panel">
      <div class="demo-panel__header">
        <div>
          <h2>JSON View</h2>
          <p>Read-only JSON tree with collapse controls, virtualization, and low-node CSS-based indentation guides.</p>
        </div>
      </div>

      <div class="demo-grid">
        <div class="demo-card textarea">
          <h3>Source</h3>
          <textarea v-model="source" class="demo-source"></textarea>
          <p v-if="parseError" class="demo-error">{{ parseError }}</p>
        </div>

        <div class="demo-card options">
          <h3>Options</h3>
          <label class="demo-option">
            <span>Virtual</span>
            <input v-model="options.virtual" type="checkbox" />
          </label>
          <label class="demo-option">
            <span>Dynamic height</span>
            <input v-model="options.dynamicHeight" type="checkbox" />
          </label>
          <label class="demo-option">
            <span>Show line guides</span>
            <input v-model="options.showLine" type="checkbox" />
          </label>
          <label class="demo-option">
            <span>Show line numbers</span>
            <input v-model="options.showLineNumber" type="checkbox" />
          </label>
          <label class="demo-option">
            <span>Show toggles</span>
            <input v-model="options.showIcon" type="checkbox" />
          </label>
          <label class="demo-option">
            <span>Show collapsed length</span>
            <input v-model="options.showLength" type="checkbox" />
          </label>
          <label class="demo-option">
            <span>Theme</span>
            <select v-model="options.theme">
              <option value="light">light</option>
              <option value="dark">dark</option>
            </select>
          </label>
          <label class="demo-option">
            <span>Indent</span>
            <input v-model.number="options.indent" min="1" max="6" type="number" />
          </label>
          <label class="demo-option">
            <span>Height</span>
            <input v-model.number="options.height" min="180" max="720" step="20" type="number" />
          </label>
          <label class="demo-option">
            <span>Item height</span>
            <input v-model.number="options.itemHeight" min="18" max="48" step="2" type="number" />
          </label>
          <label class="demo-option">
            <span>Collapse deeper than</span>
            <input v-model.number="options.deep" min="1" max="8" type="number" />
          </label>
          <label class="demo-option">
            <span>Collapse length over</span>
            <input v-model.number="options.collapsedNodeLength" min="1" max="40" type="number" />
          </label>
        </div>
      </div>
    </section>

    <section class="demo-panel">
      <div class="demo-panel__header">
        <div>
          <h3>Parsed Input</h3>
          <p>Primary viewer using the current source text, including the built-in copy menu and one custom item.</p>
        </div>
      </div>

      <JsonView
        :data="parsedData"
        :virtual="options.virtual"
        :dynamic-height="options.dynamicHeight"
        :show-line="options.showLine"
        :show-line-number="options.showLineNumber"
        :show-icon="options.showIcon"
        :show-length="options.showLength"
        :theme="options.theme"
        :indent="options.indent"
        :height="options.height"
        :item-height="options.itemHeight"
        :deep="normalizeNumber(options.deep)"
        :collapsed-node-length="normalizeNumber(options.collapsedNodeLength)"
        :menu-items="menuItems"
      />
    </section>

    <section class="demo-panel">
      <div class="demo-panel__header">
        <div>
          <h3>Large Virtualized Payload</h3>
          <p>Stress case with a large array to exercise flattening and virtualization.</p>
        </div>
      </div>

      <JsonView
        :data="largeData"
        :virtual="true"
        :dynamic-height="false"
        :show-line="true"
        :show-line-number="true"
        :show-icon="true"
        :show-length="true"
        theme="dark"
        :indent="2"
        :height="420"
        :item-height="22"
        :collapsed-node-length="18"
      />
    </section>

    <section class="demo-panel">
      <div class="demo-panel__header">
        <div>
          <h3>JS Collections And Cycles</h3>
          <p>Verifies support for `Map`, `Set`, and cyclic references that are not representable in JSON text.</p>
        </div>
      </div>

      <JsonView
        :data="jsData"
        :virtual="true"
        :dynamic-height="true"
        :show-line="true"
        :show-line-number="true"
        :show-icon="true"
        :show-length="true"
        theme="light"
        :indent="2"
        :height="320"
        :item-height="20"
      />
    </section>
  </div>
</template>

<script setup lang="ts">
import { computed, reactive, ref } from "vue";
import { JsonView } from "noob-mengyxu";

const initialData = {
  id: "case-1024",
  owner: {
    id: 7,
    name: "Avery Stone",
    roles: ["admin", "ops", "audit"],
  },
  metrics: {
    total: 1532,
    successRate: 0.9821,
    active: true,
    notes: null,
  },
  timeline: [
    { at: 1710000000, event: "created" },
    { at: 1710003600, event: "validated" },
    { at: 1710007200, event: "published" },
  ],
  payload: {
    summary: "This is a longer string intended to demonstrate optional dynamic row height handling in the viewer.",
    tags: ["alpha", "beta", "gamma"],
    nested: {
      one: { two: { three: { four: "deep value" } } },
      mixed: [1, "two", false, null, { end: true }],
    },
  },
};

const source = ref(JSON.stringify(initialData, null, 2));
const parseError = ref("");

const options = reactive({
  virtual: true,
  dynamicHeight: true,
  showLine: true,
  showLineNumber: false,
  showIcon: true,
  showLength: true,
  theme: "light" as "light" | "dark",
  indent: 2,
  height: 360,
  itemHeight: 20,
  deep: 4,
  collapsedNodeLength: 10,
});

const parsedData = computed(() => {
  try {
    parseError.value = "";
    return JSON.parse(source.value);
  } catch (error) {
    parseError.value = error instanceof Error ? error.message : String(error);
    return initialData;
  }
});

const largeData = computed(() => ({
  meta: {
    generatedAt: "2026-04-09T11:30:00Z",
    totalRows: 1500,
  },
  items: Array.from({ length: 1500 }, (_, index) => ({
    id: index + 1,
    group: `batch-${Math.floor(index / 50)}`,
    active: index % 3 === 0,
    score: Number((Math.sin(index / 10) * 100).toFixed(3)),
    tags: [`tag-${index % 5}`, `tag-${index % 7}`, `tag-${index % 11}`],
    details: {
      owner: `user-${index % 23}`,
      region: ["us", "eu", "apac"][index % 3],
      comment: `Item ${
        index + 1
      } contains enough text to keep the viewer honest about large arrays and scrolling behavior.`,
    },
  })),
}));

const jsData = computed(() => {
  const owner = {
    id: 7,
    name: "Avery Stone",
  };

  const cycleRoot: Record<string, unknown> = {
    id: "cyclic-root",
    owner,
  };

  const linked = {
    parent: cycleRoot,
    owner,
  };

  cycleRoot.self = cycleRoot;
  cycleRoot.linked = linked;

  return {
    owner,
    pairMap: new Map<unknown, unknown>([
      ["owner", owner],
      [42, { status: "ok" }],
      [{ kind: "object-key" }, new Set(["alpha", "beta"])],
    ]),
    valueSet: new Set<unknown>(["alpha", 42, owner, cycleRoot]),
    cycleRoot,
  };
});

const menuItems = ({ value, copy }: { value: unknown; copy: (text: string) => Promise<boolean> }) => [
  {
    key: "copy-type",
    label: "Copy Type",
    onSelect: async () => {
      const type =
        value === null ? "null" : Array.isArray(value) ? "array" : typeof value === "object" ? "object" : typeof value;
      await copy(type);
    },
  },
];

function normalizeNumber(value: number) {
  return Number.isFinite(value) && value > 0 ? value : undefined;
}
</script>

<style lang="scss" scoped>
.json-view-demo {
  display: grid;
  gap: 20px;
  padding: 20px;
}

.demo-panel {
  background: #fff;
  border: 1px solid #dfe4ea;
  border-radius: 14px;
  display: grid;
  gap: 16px;
  padding: 18px;
}

.demo-panel__header h2,
.demo-panel__header h3 {
  margin: 0 0 6px;
}

.demo-panel__header p {
  color: #5b6472;
  margin: 0;
}

.demo-grid {
  display: flex;
  flex-direction: row;
  gap: 16px;
}

.demo-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.demo-card.textarea {
  flex: 1;
  min-width: 0;
}

.demo-card.options {
  /* margin: 20px; */
}

.demo-card h3 {
  margin: 0;
}

.demo-source {
  border: 1px solid #cbd5e1;
  border-radius: 10px;
  font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, monospace;
  min-height: 260px;
  padding: 12px;
  resize: vertical;
}

.demo-option {
  align-items: center;
  display: grid;
  gap: 10px;
  grid-template-columns: 1fr auto;
}

.demo-option input,
.demo-option select {
  min-width: 96px;
}

.demo-error {
  color: #b42318;
  margin: 0;
}

@media (max-width: 960px) {
  .demo-grid {
    grid-template-columns: 1fr;
  }
}
</style>
