<!DOCTYPE html>
<html lang="th">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Omniscience Tracer</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gojs/2.3.11/go.js"></script>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
      @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@400;500&display=swap');

      body {
        font-family: 'Noto Sans Thai', sans-serif;
      }

      #myDiagramDiv {
        height: calc(100vh - 64px);
        overflow: auto;
      }
    </style>
  </head>
  <body class="bg-gray-50">
    <div id="secretKeyModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
      <div class="bg-white p-6 rounded-lg shadow-xl max-w-md w-full">
        <h2 class="text-xl font-semibold mb-4">X-Secret</h2>
        <input type="password" id="secretKeyInput" placeholder="Secret Key" class="w-full p-2 border rounded mb-4">
        <button id="secretKeySubmitBtn" class="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600">Authenticate</button>
      </div>
    </div>
    <div class="p-4 bg-white shadow flex items-center">
      <input type="text" id="pidInput" placeholder="เลขบัตรประชาชน" class="border p-2 rounded flex-grow mr-2">
      <button id="generateBtn" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors">Start</button>
      <button id="stopBtn" class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition-colors ml-2 hidden">Stop</button>
      <div class="ml-4">
        <button id="toggleLogBtn" class="px-3 py-2 bg-gray-200 rounded flex items-center">📜 Log <span id="logCount" class="ml-2 bg-blue-500 text-white rounded-full px-2 text-xs">0</span></button>
      </div>
    </div>
    <div id="errorMsg" class="text-red-500 text-center mb-4 hidden"></div>
    <div id="myDiagramDiv" class="bg-white"></div>
    <div id="logContainer" class="fixed bottom-4 right-4 w-96 bg-white rounded-lg shadow-lg max-h-96 overflow-hidden transition-all duration-300 ease-in-out z-40" style="height:0">
      <div class="p-2 bg-gray-100 flex justify-between items-center">
        <span class="font-semibold">Log</span>
        <button id="clearLogBtn" class="text-sm bg-red-500 text-white px-2 py-1 rounded">Clear</button>
      </div>
      <div id="log" class="p-2 overflow-y-auto" style="max-height:300px"></div>
    </div>
    <script>
const app = new class {
  constructor() {
    this.myDiagram = null;
    this.accessToken = '';
    this.secretKey = '';
    this.relationships = new Map();
    this.processedPids = new Set();
    this.shouldStop = false;
    this.requestQueue = [];
    this.processing = false;
    this.parentInfo = new Map(); // Store parent information
    this.initializeEventListeners();
  }

  initializeEventListeners() {
    document.getElementById('secretKeySubmitBtn').addEventListener('click', () => this.handleSecretKeySubmit());
    document.getElementById('generateBtn').addEventListener('click', () => this.generateTree());
    document.getElementById('stopBtn').addEventListener('click', () => this.stopProcessing());
    document.getElementById('toggleLogBtn').addEventListener('click', () => this.handleToggleLog());
    document.getElementById('clearLogBtn').addEventListener('click', () => this.handleClearLog());
    document.getElementById('secretKeyInput').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') this.handleSecretKeySubmit();
    });
    document.getElementById('pidInput').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') this.generateTree();
    });
  }

  showStopButton() {
    document.getElementById('generateBtn').classList.add('hidden');
    document.getElementById('stopBtn').classList.remove('hidden');
  }

  hideStopButton() {
    document.getElementById('stopBtn').classList.add('hidden');
    document.getElementById('generateBtn').classList.remove('hidden');
  }

  stopProcessing() {
    this.shouldStop = true;
    this.requestQueue = [];
    this.processing = false;
    this.log('STOP', '🛑');
    this.hideStopButton();
    document.getElementById('generateBtn').disabled = false;
    document.getElementById('generateBtn').textContent = 'ค้นหา';
  }

  log(msg, emoji = '📝') {
    const logDiv = document.getElementById('log');
    const div = document.createElement('div');
    div.innerHTML = `
      <div class="flex items-center mb-1 p-1 bg-gray-50 rounded">
        <span class="mr-2">${emoji}</span>
        <span class="text-sm">${new Date().toLocaleTimeString()} ${msg}</span>
      </div>`;
    logDiv.insertBefore(div, logDiv.firstChild);
    document.getElementById('logCount').textContent = parseInt(document.getElementById('logCount').textContent) + 1;
  }

  handleToggleLog() {
    const container = document.getElementById('logContainer');
    container.style.height = container.style.height === '0px' || container.style.height === '' ? '400px' : '0';
  }

  handleClearLog() {
    document.getElementById('log').innerHTML = '';
    document.getElementById('logCount').textContent = '0';
  }

  showError(message) {
    const errorDiv = document.getElementById('errorMsg');
    errorDiv.textContent = message;
    errorDiv.classList.remove('hidden');
    this.log(message, '❌');
  }

  async handleSecretKeySubmit() {
    try {
      const key = document.getElementById('secretKeyInput').value.trim();
      if (!key) {
        alert('กรุณาใส่ Secret Key');
        return;
      }
      this.secretKey = key;
      await this.init();
      document.getElementById('secretKeyModal').style.display = 'none';
    } catch (error) {
      alert('เกิดข้อผิดพลาดในการเริ่มต้นระบบ');
      console.error(error);
    }
  }

  async getToken() {
    try {
      this.log('Authenticating..', '🔑');
      const response = await fetch('https://omniscience.wenhe.bar/token', {
        method: 'POST',
        headers: {
          'x-secret': this.secretKey
        }
      });
      const data = await response.json();
      if (!data.access_token) throw new Error('Invalid token response');
      this.accessToken = data.access_token;
      this.log('Authenticated', '✅');
    } catch (error) {
      this.log('Auth Error: ' + error.message, '❌');
      throw error;
    }
  }

  async fetchProfile(pid) {
    try {
      this.log(`PROFILE_RQ ${pid}`, '👤');
      const response = await fetch('https://omniscience.wenhe.bar/v1/nhso/get_profile', {
        method: 'POST',
        headers: {
          'authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ pid })
      });
      const data = await response.json();
      if (data.status === 'SUCCESS') this.log(`PROFILE_OK ${pid}`, '✅');
      return data;
    } catch (error) {
      this.log(`PROFILE_ERROR: ${error.message}`, '❌');
      return null;
    }
  }

  async fetchChildren(pid, isFemale) {
    try {
      this.log(`CHILD_RQ ${pid}`, '👶');
      const endpoint = isFemale ? 'pop_mother' : 'pop_father';
      const response = await fetch(`https://omniscience.wenhe.bar/v1/isoc/${endpoint}`, {
        method: 'POST',
        headers: {
          'authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ pid })
      });
      const data = await response.json();
      const children = data.status === 'SUCCESS' && Array.isArray(data.data) ? data.data : [];
      this.log(`CHILD_OK ${children.length}`, '✅');
      return children;
    } catch (error) {
      this.log(`CHILD_ERROR: ${error.message}`, '❌');
      return [];
    }
  }

  async fetchImage(pid) {
    if (!pid) return null;
    try {
      const response = await fetch('https://omniscience.wenhe.bar/v1/isoc/pop_image', {
        method: 'POST',
        headers: {
          'authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ pid })
      });
      const data = await response.json();
      return data.status === 'SUCCESS' ? data.data.image : null;
    } catch {
      return null;
    }
  }

  initializeDiagram() {
    const $ = go.GraphObject.make;
    this.myDiagram = $(go.Diagram, "myDiagramDiv", {
      initialContentAlignment: go.Spot.Center,
      "animationManager.isEnabled": false,
      "undoManager.isEnabled": true,
      allowMove: true,
      allowDragOut: true,
      layout: $(go.LayeredDigraphLayout, {
        direction: 90,
        layerSpacing: 150,
        columnSpacing: 100
      })
    });

    this.myDiagram.nodeTemplate = $(go.Node, "Vertical", {
      selectionAdorned: false,
      cursor: "move",
      mouseEnter: (e, obj) => obj.findObject("SHAPE").scale = 1.1,
      mouseLeave: (e, obj) => obj.findObject("SHAPE").scale = 1.0
    }, $(go.Panel, "Auto",
      $(go.Shape, "RoundedRectangle", {
        name: "SHAPE",
        fill: "white",
        stroke: "#ccc",
        strokeWidth: 2,
        width: 280,
        height: 120
      }, new go.Binding("stroke", "gender", gender => gender === "2" ? "#EC4899" : "#3B82F6")),
      $(go.Panel, "Horizontal", {
        margin: 10
      },
        $(go.Picture, {
          width: 80,
          height: 80,
          background: "#F3F4F6",
          margin: new go.Margin(0, 10, 0, 0)
        }, new go.Binding("source", "image")),
        $(go.Panel, "Vertical", {
          alignment: go.Spot.Left
        },
          $(go.Panel, "Horizontal",
            $(go.TextBlock, {
              font: "500 16px Noto Sans Thai",
              maxLines: 1,
              overflow: go.TextBlock.OverflowEllipsis
            }, new go.Binding("text", "name")),
            $(go.TextBlock, {
              font: "500 16px Noto Sans Thai",
              stroke: "#EF4444",
              margin: new go.Margin(0, 0, 0, 8)
            }, new go.Binding("text", "deceased", d => d ? " (เสียชีวิต)" : ""))
          ),
          $(go.TextBlock, {
            font: "14px Noto Sans Thai",
            stroke: "#6B7280",
            margin: new go.Margin(4, 0, 4, 0)
          }, new go.Binding("text", "pid")),
          $(go.TextBlock, {
            font: "14px Noto Sans Thai",
            stroke: "#4B5563"
          }, new go.Binding("text", "age", age => `${age} ปี`))
        )
      )
    ));

    this.myDiagram.linkTemplate = $(go.Link, {
      routing: go.Link.AvoidsNodes,
      corner: 10
    },
      $(go.Shape, {
        strokeWidth: 2
      }, new go.Binding("stroke", "incomplete", inc => inc ? "#EF4444" : "#CBD5E1")),
      $(go.Panel, "Auto",
        $(go.Shape, "RoundedRectangle", {
          fill: "white",
          stroke: "#CBD5E1",
          strokeWidth: 1
        }),
        $(go.TextBlock, {
          font: "14px Noto Sans Thai",
          margin: 5,
          background: "white"
        }, new go.Binding("text", "relationship", rel => {
          if (rel.includes("incomplete")) {
            return rel.replace("incomplete", "") + " (ไม่สมบูรณ์)";
          }
          return rel;
        }))
      )
    );

    this.myDiagram.model = new go.GraphLinksModel([], []);
  }

  addNode(nodeData) {
    const existingNode = this.myDiagram.model.findNodeDataForKey(nodeData.key);
    if (!existingNode) {
      this.myDiagram.model.addNodeData(nodeData);
    }
  }

  addRelationship(fromPid, toPid, relationship, incomplete = false) {
    const key = fromPid < toPid ? `${fromPid}-${toPid}` : `${toPid}-${fromPid}`;
    if (!this.relationships.has(key)) {
      this.relationships.set(key, true);
      this.myDiagram.model.addLinkData({
        from: fromPid,
        to: toPid,
        relationship: incomplete ? relationship + " (ไม่สมบูรณ์)" : relationship,
        incomplete: incomplete
      });
    }
  }

  isValidId(id) {
    if (!id) return false;
    const cleanId = id.trim().replace(/\s+/g, '');
    return cleanId.length === 13 && !/^0+$/.test(cleanId);
  }

  async processQueue() {
    if (this.shouldStop || this.processing || this.requestQueue.length === 0) return;
    this.processing = true;
    const { pid, relatedToPid, relationshipType } = this.requestQueue.shift();

    try {
      if (!this.processedPids.has(pid)) {
        const profile = await this.fetchProfile(pid);
        if (profile?.data) {
          const person = profile.data.personData;
          const isFemale = person.sex === "2";

          let image = null;
          if (person.age.years >= 10) {
            image = await this.fetchImage(pid);
            if (image) image = `data:image/png;base64,${image}`;
          }

          // Add the person to the diagram
          this.addNode({
            key: pid,
            name: `${person.fname} ${person.lname}`,
            gender: person.sex,
            age: person.age.years,
            image: image,
            pid: pid,
            deceased: profile.data.fundStatus?.statusDesc?.includes("จำหน่าย") ||
              profile.data.fundStatus?.statusDesc?.includes("ตาย")
          });

          // Store parent information for later reference
          this.parentInfo.set(pid, {
            fatherId: person.fatherId,
            motherId: person.motherId
          });

          // Add relationship with the related person if any
          if (relatedToPid) {
            // If this person is a child being processed, and we have their parent IDs
            if (relationshipType === 'ลูก') {
              const hasKnownFather = this.isValidId(person.fatherId);
              const hasKnownMother = this.isValidId(person.motherId);
              const isIncomplete = !hasKnownFather || !hasKnownMother;
              
              // Link to the current parent
              this.addRelationship(relatedToPid, pid, relationshipType, isIncomplete);
              
              // If this parent is the father and we know the mother, add mother relationship
              if (hasKnownMother && this.parentInfo.has(relatedToPid)) {
                const parentData = this.parentInfo.get(relatedToPid);
                if (parentData.gender === "1") { // if related person is father
                  this.addRelationship(person.motherId, pid, 'ลูก', false);
                }
              }
              
              // If this parent is the mother and we know the father, add father relationship
              if (hasKnownFather && this.parentInfo.has(relatedToPid)) {
                const parentData = this.parentInfo.get(relatedToPid);
                if (parentData.gender === "2") { // if related person is mother
                  this.addRelationship(person.fatherId, pid, 'ลูก', false);
                }
              }
            } else {
              this.addRelationship(relatedToPid, pid, relationshipType, false);
            }
          }

          // Handle known parents from profile
          const hasKnownFather = this.isValidId(person.fatherId);
          const hasKnownMother = this.isValidId(person.motherId);

          // Store parent information and gender for later reference
          this.parentInfo.set(pid, {
            fatherId: person.fatherId,
            motherId: person.motherId,
            gender: person.sex
          });

          if (hasKnownFather && !this.processedPids.has(person.fatherId)) {
            this.requestQueue.push({
              pid: person.fatherId,
              relatedToPid: pid,
              relationshipType: 'พ่อ'
            });
          }

          if (hasKnownMother && !this.processedPids.has(person.motherId)) {
            this.requestQueue.push({
              pid: person.motherId,
              relatedToPid: pid,
              relationshipType: 'แม่'
            });
          }

          // If we don't have both parents, fetch children using API
          if (!hasKnownFather || !hasKnownMother) {
            const children = await this.fetchChildren(pid, isFemale);
            if (Array.isArray(children)) {
              for (const child of children) {
                if (!this.processedPids.has(child.pid)) {
                  // Add child to queue with incomplete status if missing parent info
                  this.requestQueue.push({
                    pid: child.pid,
                    relatedToPid: pid,
                    relationshipType: 'ลูก'
                  });
                }
              }
            }
          }

          this.processedPids.add(pid);
        }
      }
    } catch (error) {
      this.log(`PROC_ERROR ${pid}: ${error.message}`, '❌');
    }

    this.processing = false;
    await this.processQueue();
  }

  async init() {
    try {
      await this.getToken();
      if (!this.accessToken) throw new Error('INIT_ERROR: Authentication failed');
      this.log('READY', '✅');
      this.initializeDiagram();
    } catch (error) {
      this.showError('INIT_ERROR');
      this.log('INIT_ERROR: ' + error.message, '❌');
      throw error;
    }
  }

  async generateTree() {
    try {
      const pid = document.getElementById('pidInput').value.trim();
      if (pid.length !== 13) {
        this.showError('กรุณากรอกเลขประจำตัวประชาชน 13 หลัก');
        return;
      }

      const generateBtn = document.getElementById('generateBtn');
      generateBtn.disabled = true;
      generateBtn.textContent = 'Searching...';
      this.showStopButton();

      if (!this.accessToken) {
        await this.getToken();
        if (!this.accessToken) throw new Error('Could not get access token');
      }

      this.shouldStop = false;
      this.relationships = new Map();
      this.processedPids = new Set();
      this.parentInfo = new Map();
      this.requestQueue = [{
        pid,
        relatedToPid: null,
        relationshipType: null
      }];
      this.processing = false;
      this.myDiagram.model = new go.GraphLinksModel([], []);

      await this.processQueue();

      if (!this.shouldStop) {
        this.myDiagram.zoomToFit();
        this.log('FINISHED', '🌳');
      }
    } catch (error) {
      this.showError('GENERATION_FAILED');
      this.log('ERROR: ' + error.message, '❌');
    } finally {
      this.hideStopButton();
      const generateBtn = document.getElementById('generateBtn');
      generateBtn.disabled = false;
      generateBtn.textContent = 'Start';
    }
  }
};
    </script>
  </body>
</html>