# Copyright (C) 2007-2026 Ahmet Öztürk (aoz_2@yahoo.com)
#
# This file is part of Lifeograph.
#
# Lifeograph is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Lifeograph is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Lifeograph.  If not, see <http://www.gnu.org/licenses/>.

from LoG import *
import json
import datetime
import markdown_parsing as mdp

NAME="ChatGPT History Json (Experimental)"
VERSION="0.1"
CATEGORY=SCtg.IMPORTER

_NOW = Date.get_now()

def run(path):
    dm = Diary.get_main()

    with open(path, "r", encoding="utf-8") as f:
        data = json.load(f)

    entry_root = dm.create_entry_rel(None, True, "ChatGPT Import")
    entry_root.append_paragraph(f"Imported on {Date.format_str(_NOW, 'F, T')}")

    # export may contain either:
    # - a list of conversations
    # - or a single conversation
    if isinstance(data, dict):
        conversations = [data]
    else:
        conversations = data

    for conv in conversations:
        title = conv.get("title", "Untitled Conversation")
        if not title: #skip empty conversatons <- TODO CHECK
            continue

        mapping   = conv.get("mapping", {})
        messages  = _get_nodes(mapping)
        date_str  = conv.get("create_time") or ""
        uuid      = conv.get("conversation_id", "")

        _import_entry(dm, entry_root, title, _timestamp_to_datev(date_str), messages, uuid)

    show_message("Import complete!")
    refresh_panels()

    return True

# HELPERS
def _get_nodes(mapping):
    proto_nodes = []

    # collect all real messages:
    for node_id, node in mapping.items():

        msg = node.get("message")

        if msg is None:
            continue

        proto_nodes.append({
            "id": node_id,
            "parent": node.get("parent"),
            "message": msg
        })

    # build quick lookup:
    by_parent = {}

    for node in proto_nodes:
        parent = node["parent"]

        if parent not in by_parent:
            by_parent[parent] = []

        by_parent[parent].append(node)

    # find first actual message:
    current = None

    for node in proto_nodes:
        parent = node["parent"]

        # first message's parent usually points to the artificial root:
        if parent not in [n["id"] for n in proto_nodes]:
            current = node
            break

    if current is None:
        return []

    sequence = []

    visited = set()

    while current:
        node_id = current["id"]

        if node_id in visited:
            print("Cycle detected")
            break

        visited.add(node_id)

        sequence.append(current["message"])

        children = by_parent.get(node_id, [])

        if not children:
            break

        # choose latest child if branching exists:
        best = None
        best_time = -1

        for child in children:
            t = child["message"].get(
                "create_time",
                0
            ) or 0

            if t > best_time:
                best = child
                best_time = t

        current = best

    # fill in the actual nodes to be returned:
    nodes = []
    for message in sequence:
        author = (
            message.get("author", {})
                   .get("role", "unknown")
        )

        content = _extract_text(message)

        if not content.strip():
            continue

        timestamp = message.get("create_time")

        nodes.append({
            "author": author,
            "content": content,
            "date": _timestamp_to_datev(timestamp)
        })

    return nodes

def _extract_text(message):
    content = message.get("content", {})

    parts = content.get("parts", [])

    texts = []

    for p in parts:
        if isinstance(p, str):
            texts.append(p)

    return "\n".join(texts)

def _timestamp_to_datev(timestamp):
    if timestamp:
        try:
            dt = datetime.datetime.fromtimestamp(timestamp)
        except (ValueError, TypeError):
            pass
        else:
            return Date.make(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
    return _NOW


def _import_entry(diary, entry_root, title, date_c, messages, uuid):
    entry = diary.create_entry_rel(entry_root, True, title)
    entry.set_creation_date(date_c)
    para = entry.append_paragraph(uuid)
    para.add_link(f"https://chatgpt.com/c/{uuid}", 0, para.get_size())
    #para.add_format(VT.FMT.FADED)

    for msg in messages:
        author  = msg["author"]
        date    = msg["date"]
        text    = msg["content"]

        para = entry.append_paragraph(author)
        para.set_alignment(VT.PA.RIGHT if author == "user" else VT.PA.LEFT)
        para.set_hrule(True)
        para.set_heading_level(VT.PHS.LARGE)

        para = entry.append_paragraph(f"{Date.format_str(date, "F, T")}")
        para.set_alignment(VT.PA.RIGHT if author == "user" else VT.PA.LEFT)

        mdp.import_markdown(entry, text)

        entry.append_paragraph("") # add an empty para for stronger separation

