/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

import {
  Graph,
  Vertex,
  Edge,
  Property,
  VertexProperty,
  Path,
  Tree,
  CompositePDT,
  PrimitivePDT
} from '../../../lib/structure/graph.js';
import { direction, merge, t } from '../../../lib/process/traversal.js';

/*
 * Unsupported types (no map entries, .gbin files exist but types not implemented):
 * - single-byte-char, two-byte-char, three-byte-char, four-byte-char (Char 0x80 not implemented)
 * - pos-bigdecimal, neg-bigdecimal, zero-bigdecimal, scale-zero-bigdecimal,
 *   negative-scale-bigdecimal, small-decimal-bigdecimal (BigDecimal type not implemented)
 * - forever-duration, zero-duration, positive-duration, negative-duration,
 *   nanos-duration (Duration type not implemented)
 */

const model = {};

// BigInteger values
model['pos-biginteger'] = BigInt('123456789987654321123456789987654321');
model['neg-biginteger'] = BigInt('-123456789987654321123456789987654321');
model['zero-biginteger'] = 0n;
model['sign-boundary-pos-biginteger'] = 128n;
model['sign-boundary-neg-biginteger'] = -129n;

// Provider-defined type values
model['uint8-primitive-pdt'] = new PrimitivePDT('Uint8', '10');
model['point-composite-pdt'] = new CompositePDT('Point', { x: 1, y: 2 });

// Byte values (JS has no byte type, deserializes to Number)
model['min-byte'] = -128;
model['max-byte'] = 127;

// Binary values
model['empty-binary'] = Buffer.from('', 'utf-8');
model['str-binary'] = Buffer.from('some bytes for you', 'utf-8');

// Double values
model['max-double'] = 1.7976931348623157e+308;
model['min-double'] = 5e-324;
model['neg-max-double'] = -1.7976931348623157e+308;
model['neg-min-double'] = -5e-324;
model['nan-double'] = NaN;
model['pos-inf-double'] = Infinity;
model['neg-inf-double'] = -Infinity;
model['neg-zero-double'] = -0;

// Float values (JS has no float type, deserializes to Number with IEEE 754 double representation)
model['max-float'] = 3.4028234663852886e+38;
model['min-float'] = 1.401298464324817e-45;
model['neg-max-float'] = -3.4028234663852886e+38;
model['neg-min-float'] = -1.401298464324817e-45;
model['nan-float'] = NaN;
model['pos-inf-float'] = Infinity;
model['neg-inf-float'] = -Infinity;
model['neg-zero-float'] = -0;

// Null
model['unspecified-null'] = null;
model['null-int'] = null;
model['null-long'] = null;
model['null-string'] = null;
model['null-list'] = null;
model['null-map'] = null;
model['null-set'] = null;

// Boolean values
model['true-boolean'] = true;
model['false-boolean'] = false;

// String values
model['single-byte-string'] = 'abc';
model['mixed-string'] = 'abc\u0391\u0392\u0393';
model['empty-string'] = '';

// List values
model['var-bulklist'] = ['marko', 'josh', 'josh'];
model['empty-bulklist'] = [];
model['var-type-list'] = [1, 'person', true, null];
model['empty-list'] = [];

// Edge values
model['traversal-edge'] = new Edge(
  13,
  new Vertex(1, 'person'),    // outV (first in JS constructor)
  'develops',
  new Vertex(10, 'software'), // inV
  [new Property('since', 2009)]
);
model['no-prop-edge'] = new Edge(
  13,
  new Vertex(1, 'person'),
  'develops',
  new Vertex(10, 'software')
);

// Integer values
model['max-int'] = 2147483647;
model['min-int'] = -2147483648;

// Long values
model['max-long'] = 9223372036854775807n;
model['min-long'] = -9223372036854775808n;

// Map values
const dateKey = new Date(Date.UTC(1970, 0, 1, 0, 24, 41, 295)); // 1481295 ms
model['var-type-map'] = new Map([
  [null, null],
  [[1, 2, 3], dateKey],
  [dateKey, 'red'],
  ['test', 123]
]);
model['empty-map'] = new Map();
model['ordered-string-int-map'] = new Map([
  ['delta', 4],
  ['alpha', 1],
  ['charlie', 3],
  ['bravo', 2],
  ['echo', 5],
  ['foxtrot', 6]
]);

// Path values
model['traversal-path'] = new Path(
  [new Set(), new Set(), new Set()],
  [new Vertex(1, 'person'), new Vertex(10, 'software'), new Vertex(11, 'software')]
);
model['empty-path'] = new Path([], []);
model['path-zero-labels'] = new Path([new Set()], ['marko']);
model['path-multiple-labels'] = new Path([new Set(['a', 'b'])], ['marko']);

// Tree values
// tree for g.V(10).out().tree(): v[10] -> v[11]
const traversalTree = new Tree();
traversalTree.getOrCreateChild(new Vertex(10, 'software')).getOrCreateChild(new Vertex(11, 'software'));
model['traversal-tree'] = traversalTree;
model['empty-tree'] = new Tree();
const treeNullKey = new Tree();
treeNullKey.getOrCreateChild(null);
model['tree-null-key'] = treeNullKey;
const treeMixedKeyTypes = new Tree();
treeMixedKeyTypes.getOrCreateChild('name');
treeMixedKeyTypes.getOrCreateChild(123);
model['tree-mixed-key-types'] = treeMixedKeyTypes;
const treeDeepNesting = new Tree();
treeDeepNesting.getOrCreateChild('root').getOrCreateChild('branch').getOrCreateChild('leaf');
model['tree-deep-nesting'] = treeDeepNesting;

// Complex path with nested properties
const propPathVertex = new Vertex(1, 'person', [
  new VertexProperty(0, 'name', 'marko'),
  new VertexProperty(6, 'location', 'san diego', [
    new Property('startTime', 1997),
    new Property('endTime', 2001)
  ]),
  new VertexProperty(7, 'location', 'santa cruz', [
    new Property('startTime', 2001),
    new Property('endTime', 2004)
  ]),
  new VertexProperty(8, 'location', 'brussels', [
    new Property('startTime', 2004),
    new Property('endTime', 2005)
  ]),
  new VertexProperty(9, 'location', 'santa fe', [
    new Property('startTime', 2005)
  ])
]);
const propPathVertex2 = new Vertex(10, 'software', [
  new VertexProperty(4, 'name', 'gremlin')
]);
const propPathVertex3 = new Vertex(11, 'software', [
  new VertexProperty(5, 'name', 'tinkergraph')
]);
model['prop-path'] = new Path(
  [new Set(), new Set(), new Set()],
  [propPathVertex, propPathVertex2, propPathVertex3]
);

// Property values (no parent param in JS)
model['edge-property'] = new Property('since', 2009);
model['null-property'] = new Property('', null);

// Set values
model['var-type-set'] = new Set([2, 'person', true, null]);
model['empty-set'] = new Set();

// Short values (JS has no short type, deserializes to Number)
model['max-short'] = 32767;
model['min-short'] = -32768;

// UUID values (plain strings in JS)
model['specified-uuid'] = '41d2e28a-20a4-4ab0-b379-d810dede3786';
model['nil-uuid'] = '00000000-0000-0000-0000-000000000000';

// Vertex values
model['no-prop-vertex'] = new Vertex(1, 'person');

// VertexProperty values (no parent param, key equals label)
model['traversal-vertexproperty'] = new VertexProperty(0, 'name', 'marko');
model['meta-vertexproperty'] = new VertexProperty(1, 'person', 'stephen', [new Property('a', 'b')]);
model['set-cardinality-vertexproperty'] = new VertexProperty(1, 'person', new Set(['stephen', 'marko']), [new Property('a', 'b')]);

// Enum values
model['id-t'] = t.id;
model['out-direction'] = direction.out;
model['merge-on-create'] = merge.onCreate;
model['merge-on-match'] = merge.onMatch;
model['merge-out-v'] = merge.outV;
model['merge-in-v'] = merge.inV;

// Complex vertex with properties (from .gbin deserialization structure)
const name = new VertexProperty(0, 'name', 'marko');
const sanDiego = new VertexProperty(6, 'location', 'san diego', [
  new Property('startTime', 1997),
  new Property('endTime', 2001)
]);
const santaCruz = new VertexProperty(7, 'location', 'santa cruz', [
  new Property('startTime', 2001),
  new Property('endTime', 2004)
]);
const brussels = new VertexProperty(8, 'location', 'brussels', [
  new Property('startTime', 2004),
  new Property('endTime', 2005)
]);
const santaFe = new VertexProperty(9, 'location', 'santa fe', [
  new Property('startTime', 2005)
]);
model['traversal-vertex'] = new Vertex(1, 'person', [name, sanDiego, santaCruz, brussels, santaFe]);
model['multi-label-vertex'] = new Vertex(1, ['person', 'employee']);
model['empty-label-vertex'] = new Vertex(1, 'vertex', [], []);

// DateTime values (invalid dates for overflow cases)
model['max-offsetdatetime'] = new Date(NaN);  // Year 999999999 overflows JS Date
model['min-offsetdatetime'] = new Date(NaN);  // Year -999999999 overflows JS Date

class CrewGraphFactory {
  static vertexProperty(id, label, value, metaProperties = []) {
    return new VertexProperty(
      id,
      label,
      value,
      metaProperties.map(([key, metaValue]) => new Property(key, metaValue))
    );
  }

  static addVertex(graph, id, label, propertySpecs) {
    const vertex = new Vertex(id, label);
    for (const spec of propertySpecs) {
      vertex.properties.push(CrewGraphFactory.vertexProperty(spec[0], spec[1], spec[2], spec[3] || []));
    }
    graph.vertices.set(id, vertex);
    return vertex;
  }

  static addEdge(graph, id, outV, label, inV, properties = []) {
    const edge = new Edge(id, outV, label, inV, properties.map(([key, value]) => new Property(key, value)));
    graph.edges.set(id, edge);
    return edge;
  }

  static create() {
    const graph = new Graph();
    const v1 = CrewGraphFactory.addVertex(graph, 1, 'person', [
      [0, 'name', 'marko'],
      [6, 'location', 'san diego', [['startTime', 1997], ['endTime', 2001]]],
      [7, 'location', 'santa cruz', [['startTime', 2001], ['endTime', 2004]]],
      [8, 'location', 'brussels', [['startTime', 2004], ['endTime', 2005]]],
      [9, 'location', 'santa fe', [['startTime', 2005]]]
    ]);
    const v7 = CrewGraphFactory.addVertex(graph, 7, 'person', [
      [1, 'name', 'stephen'],
      [10, 'location', 'centreville', [['startTime', 1990], ['endTime', 2000]]],
      [11, 'location', 'dulles', [['startTime', 2000], ['endTime', 2006]]],
      [12, 'location', 'purcellville', [['startTime', 2006]]]
    ]);
    const v8 = CrewGraphFactory.addVertex(graph, 8, 'person', [
      [2, 'name', 'matthias'],
      [13, 'location', 'bremen', [['startTime', 2004], ['endTime', 2007]]],
      [14, 'location', 'baltimore', [['startTime', 2007], ['endTime', 2011]]],
      [15, 'location', 'oakland', [['startTime', 2011], ['endTime', 2014]]],
      [16, 'location', 'seattle', [['startTime', 2014]]]
    ]);
    const v9 = CrewGraphFactory.addVertex(graph, 9, 'person', [
      [3, 'name', 'daniel'],
      [17, 'location', 'spremberg', [['startTime', 1982], ['endTime', 2005]]],
      [18, 'location', 'kaiserslautern', [['startTime', 2005], ['endTime', 2009]]],
      [19, 'location', 'aachen', [['startTime', 2009]]]
    ]);
    const v10 = CrewGraphFactory.addVertex(graph, 10, 'software', [[4, 'name', 'gremlin']]);
    const v11 = CrewGraphFactory.addVertex(graph, 11, 'software', [[5, 'name', 'tinkergraph']]);
    CrewGraphFactory.addEdge(graph, 13, v1, 'develops', v10, [['since', 2009]]);
    CrewGraphFactory.addEdge(graph, 14, v1, 'develops', v11, [['since', 2010]]);
    CrewGraphFactory.addEdge(graph, 15, v1, 'uses', v10, [['skill', 4]]);
    CrewGraphFactory.addEdge(graph, 16, v1, 'uses', v11, [['skill', 5]]);
    CrewGraphFactory.addEdge(graph, 17, v7, 'develops', v10, [['since', 2010]]);
    CrewGraphFactory.addEdge(graph, 18, v7, 'develops', v11, [['since', 2011]]);
    CrewGraphFactory.addEdge(graph, 19, v7, 'uses', v10, [['skill', 5]]);
    CrewGraphFactory.addEdge(graph, 20, v7, 'uses', v11, [['skill', 4]]);
    CrewGraphFactory.addEdge(graph, 21, v8, 'develops', v10, [['since', 2012]]);
    CrewGraphFactory.addEdge(graph, 22, v8, 'uses', v10, [['skill', 3]]);
    CrewGraphFactory.addEdge(graph, 23, v8, 'uses', v11, [['skill', 3]]);
    CrewGraphFactory.addEdge(graph, 24, v9, 'uses', v10, [['skill', 5]]);
    CrewGraphFactory.addEdge(graph, 25, v9, 'uses', v11, [['skill', 3]]);
    CrewGraphFactory.addEdge(graph, 26, v10, 'traverses', v11);
    return graph;
  }
}

model['tinker-graph'] = CrewGraphFactory.create();

export { model };
