import {Delta, DeltaFilter, DeltaV2} from '../src/core';
import {Lossless} from '../src/views';
import {RhizomeNode} from '../src/node';

describe('Lossless', () => {
  const node = new RhizomeNode();

  it('creates a lossless view of keanu as neo in the matrix', () => {
    const delta = new DeltaV2({
      creator: 'a',
      host: 'h',
      pointers: {
        actor: {"keanu": "roles"},
        role: {"neo": "actor"},
        film: {"the_matrix": "cast"},
        base_salary: 1000000,
        salary_currency: "usd"
      }
    }).toV1();

    expect(delta.pointers).toMatchObject([{
      localContext: "actor",
      target: "keanu",
      targetContext: "roles"
    }, {
      localContext: "role",
      target: "neo",
      targetContext: "actor"
    }, {
      localContext: "film",
      target: "the_matrix",
      targetContext: "cast"
    }, {
      localContext: "base_salary",
      target: 1000000
    }, {
      localContext: "salary_currency",
      target: "usd"
    }]);

    const lossless = new Lossless(node);

    lossless.ingestDelta(delta);

    expect(lossless.view()).toMatchObject({
      keanu: {
        referencedAs: ["actor"],
        propertyDeltas: {
          roles: [{
            creator: "a",
            host: "h",
            pointers: [
              {actor: "keanu"},
              {role: "neo"},
              {film: "the_matrix"},
              {base_salary: 1000000},
              {salary_currency: "usd"},
            ],
          }],
        },
      },
      neo: {
        referencedAs: ["role"],
        propertyDeltas: {
          actor: [{
            creator: "a",
            host: "h",
            pointers: [
              {actor: "keanu"},
              {role: "neo"},
              {film: "the_matrix"},
              {base_salary: 1000000},
              {salary_currency: "usd"},
            ],
          }],
        },
      },
      the_matrix: {
        referencedAs: ["film"],
        propertyDeltas: {
          cast: [{
            creator: "a",
            host: "h",
            pointers: [
              {actor: "keanu"},
              {role: "neo"},
              {film: "the_matrix"},
              {base_salary: 1000000},
              {salary_currency: "usd"},
            ],
          }],
        },
      },
    });
  });

  describe('can filter deltas', () => {
    const lossless = new Lossless(node);

    beforeAll(() => {
      lossless.ingestDelta(new Delta({
        creator: 'A',
        host: 'H',
        pointers: [{
          localContext: "1",
          target: "ace",
          targetContext: "value"
        }]
      }));

      lossless.ingestDelta(new Delta({
        creator: 'B',
        host: 'H',
        pointers: [{
          // 10 11j 12q 13k 14a
          localContext: "14",
          target: "ace",
          targetContext: "value"
        }]
      }));

      expect(lossless.view()).toMatchObject({
        ace: {
          referencedAs: ["1", "14"],
          propertyDeltas: {
            value: [{
              creator: 'A',
              host: 'H',
              pointers: [
                {"1": "ace"},
              ]
            }, {
              creator: 'B',
              host: 'H',
              pointers: [
                {"14": "ace"},
              ]
            }],
          }
        }
      });
    });

    it('filter by creator and host', () => {
      const filter: DeltaFilter = ({creator, host}) => {
        return creator === 'A' && host === 'H';
      };

      expect(lossless.view(undefined, filter)).toMatchObject({
        ace: {
          referencedAs: ["1"],
          propertyDeltas: {
            value: [{
              creator: 'A',
              host: 'H',
              pointers: [
                {"1": "ace"},
              ]
            }]
          }
        }
      });

      expect(lossless.view(["ace"], filter)).toMatchObject({
        ace: {
          referencedAs: ["1"],
          propertyDeltas: {
            value: [{
              creator: 'A',
              host: 'H',
              pointers: [
                {"1": "ace"},
              ]
            }]
          }
        }
      });
    });

    it('filter with transactions', () => {
      const losslessT = new Lossless(node);
      const transactionId = 'tx-filter-test';

      // Declare transaction with 3 deltas
      losslessT.ingestDelta(new Delta({
        creator: 'system',
        host: 'H',
        pointers: [
          { localContext: '_transaction', target: transactionId, targetContext: 'size' },
          { localContext: 'size', target: 3 }
        ]
      }));

      // A1: First delta from creator A
      losslessT.ingestDelta(new Delta({
        creator: 'A',
        host: 'H',
        pointers: [
          { localContext: '_transaction', target: transactionId, targetContext: 'deltas' },
          { localContext: 'step', target: 'process1', targetContext: 'status' },
          { localContext: 'value', target: 'started' }
        ]
      }));

      // B: Delta from creator B
      losslessT.ingestDelta(new Delta({
        creator: 'B',
        host: 'H',
        pointers: [
          { localContext: '_transaction', target: transactionId, targetContext: 'deltas' },
          { localContext: 'step', target: 'process1', targetContext: 'status' },
          { localContext: 'value', target: 'processing' }
        ]
      }));

      // Transaction incomplete - nothing should show
      const incompleteView = losslessT.view(['process1']);
      expect(incompleteView.process1).toBeUndefined();

      // A2: Second delta from creator A completes transaction
      losslessT.ingestDelta(new Delta({
        creator: 'A',
        host: 'H',
        pointers: [
          { localContext: '_transaction', target: transactionId, targetContext: 'deltas' },
          { localContext: 'step', target: 'process1', targetContext: 'status' },
          { localContext: 'value', target: 'completed' }
        ]
      }));

      // All deltas visible now
      const completeView = losslessT.view(['process1']);
      expect(completeView.process1).toBeDefined();
      expect(completeView.process1.propertyDeltas.status).toHaveLength(3);

      // Filter by creator A only
      const filterA: DeltaFilter = ({creator}) => creator === 'A';
      const filteredView = losslessT.view(['process1'], filterA);
      
      expect(filteredView.process1).toBeDefined();
      expect(filteredView.process1.propertyDeltas.status).toHaveLength(2);
      expect(filteredView.process1.propertyDeltas.status.every(d => d.creator === 'A')).toBe(true);
    });
  });
});