package main

import (
	// External
	"github.com/jmoiron/sqlx"

	// Standard
	"encoding/json"
	"time"
)

type Node struct {
	ID   int
	Name string

	ParentID int `db:"parent_id"`

	TypeID        int    `db:"type_id"`
	TypeName      string `db:"type_name"`
	TypeSchema    any    `db:"type_schema"`
	TypeSchemaRaw []byte `db:"type_schema_raw" json:"-"`
	TypeIcon      string `db:"type_icon"`

	Updated time.Time
	Data    any
	DataRaw []byte `db:"data_raw" json:"-"`

	NumChildren int `db:"num_children"`
	Children    []*Node
}

func GetNode(nodeID int) (node Node, err error) {
	row := db.QueryRowx(`
		SELECT
			n.id,
			n.name,
			n.updated,
			n.data AS data_raw,

			t.id AS type_id,
			t.name AS type_name,
			t.schema AS type_schema_raw,
			t.schema->>'icon' AS type_icon
		FROM public.node n
		INNER JOIN public.type t ON n.type_id = t.id
		WHERE
			n.id = $1
		`, nodeID)

	err = row.StructScan(&node)
	if err != nil {
		return
	}

	err = json.Unmarshal(node.TypeSchemaRaw, &node.TypeSchema)
	if err != nil {
		return
	}

	err = json.Unmarshal(node.DataRaw, &node.Data)
	if err != nil {
		return
	}
	return
}

func GetNodeTree(startNodeID, maxDepth int) (topNode *Node, err error) {
	nodes := make(map[int]*Node)
	var rows *sqlx.Rows
	rows, err = GetNodeRows(startNodeID, maxDepth)
	if err != nil {
		return
	}
	defer rows.Close()

	first := true
	for rows.Next() {
		var node Node
		err = rows.StructScan(&node)
		if err != nil {
			return
		}

		if first {
			topNode = &node
			first = false
		}

		ComposeTree(nodes, &node)
	}

	return
}

func GetNodeRows(startNodeID, maxDepth int) (rows *sqlx.Rows, err error) {
	rows, err = db.Queryx(`
		WITH RECURSIVE nodes AS (
			SELECT
				COALESCE(
					(SELECT parent FROM connection WHERE child = $1),
					0
				) AS parent_id,
				$1::int AS id,
				0 AS depth

			UNION

			SELECT
				c.parent,
				c.child,
				ns.depth+1 AS depth
			FROM connection c
			INNER JOIN nodes ns ON ns.depth < $2 AND c.parent = ns.id
		)

		SEARCH DEPTH FIRST BY id SET ordercol

		SELECT
			ns.parent_id,
			n.id,
			n.name,
			n.type_id,
			t.name AS type_name,
			COALESCE(t.schema->>'icon', '') AS type_icon,
			n.updated,
			n.data AS data_raw,
			COUNT(c.child) AS num_children
		FROM nodes ns
		INNER JOIN public.node n ON ns.id = n.id
		INNER JOIN public.type t ON n.type_id = t.id
		LEFT JOIN public.connection c ON c.parent = n.id
		GROUP BY
			ns.depth,
			ns.parent_id,
			n.id,
			t.name,
			t.schema,
			ns.ordercol
		ORDER BY ordercol
	`,
		startNodeID,
		maxDepth,
	)
	return
}

func ComposeTree(nodes map[int]*Node, node *Node) {
	if node.Children == nil {
		node.Children = []*Node{}
	}

	parentNode, found := nodes[node.ParentID]
	if found {
		if parentNode.Children == nil {
			parentNode.Children = []*Node{node}
		} else {
			parentNode.Children = append(parentNode.Children, node)
		}
	}

	nodes[node.ID] = node
}

func UpdateNode(nodeID int, data []byte) (err error) {
	_, err = db.Exec(`UPDATE public.node SET data=$2 WHERE id=$1`, nodeID, data)
	return
}
