import { Annotation , NamedCollection , JSONArray , JSONObject , } from "./internals" ; /**
 * Joins are a way to identify related annotations and be able to do
 * something to them. Using joins with collections allows for targeting
 * a portion of a document to change.
 *
 * To give a good idea of how this works, below is a block of HTML that
 * we'd like to convert into a `ResponsivePhoto` annotation. The annotation
 * looks like:
 *
 * ```
 * interface ResponsivePhoto {
 *   attributes: {
 *     caption: string;
 *     photos: string[];
 *   }
 * }
 * ```
 *
 * The HTML that we'd like to transform is:
 *
 * ```
 * <figure>
 *   <picture>
 *     <source>
 *     </source>
 *   </picture>
 *   <figcaption>
 *   </figcaption>
 * </figure>
 * ```
 *
 * First, we convert the HTML into an annotation document:
 *
 * ```
 * let doc = new HTMLSource(html);
 * ```
 *
 * Then, we query the document for the elements we care about:
 *
 * ```
 * let figures = doc.where({ type: 'figure' }).as('figure');
 * let pictures = doc.where({ type: 'picture' }).as('pictures');
 * let sources = doc.where({ type: 'source' }).as('source');
 * let captions = doc.where({ type: 'figcaption' }).as('captions');
 * ```
 *
 * After we've collected the annotations we're interested in, we can
 * compose them into objects that we can distill into the ResponsivePhoto
 * annotation:
 *
 * ```
 * let join = figures.join(pictures, (l, r) => l.start < r.start && l.end > r.end)
 *                   .join(sources, (l, r) => l.start < r.start && l.end > r.end)
 *                   .join(captions, (l, r) => l.start < r.start && l.end > r.end)
 * ```
 *
 * This join now will allow us to update the document with the new annotation:
 *
 * ```
 * join.update(({ figure, pictures, sources, captions }) => {
 *   doc.addAnnotation({
 *     type: 'responsive-photo',
 *     start: figure.start,
 *     end: figure.end,
 *     attributes: {
 *       photos: sources,
 *       caption: doc.content.slice(captions[0].start, captions[0].end)
 *     }
 *   });
 *   doc.removeAnnotations(figure, ...pictures, ...sources, ...captions);
 * });
 * ```
 */ export class Join < Left extends string , Right extends string > { private leftJoin : NamedCollection < Left > ; private _joins : Array < Record < Left , Annotation < any > > & Record < Right , Array < Annotation < any > > > > ; constructor ( leftJoin , joins ) { this . leftJoin = leftJoin ; this . _joins = joins ; } * [ Symbol . iterator ] ( ) { for ( let join of this . _joins ) { yield join ; } } forEach ( callback ) { this . _joins . forEach ( callback ) ; } get length ( ) { return this . _joins . length ; } toJSON ( ) { return [ ... this ] . map ( function joinToJSON ( join ) { let json = { } ; for ( let key in join ) { let annotation = ( join as any ) [ key ] as Annotation | Annotation [ ] ; if ( Array . isArray ( annotation ) ) { json [ key ] = annotation . map ( ( a ) => a . toJSON ( ) ) ; } else { json [ key ] = annotation . toJSON ( ) ; } } return json ; } ) ; } outerJoin < J extends string > ( rightCollection , filter ) { if ( rightCollection . document !== this . leftJoin . document ) { throw new Error ( "Joining annotations from two different documents is non-sensical. Refusing to continue." ) ; } let results = new Join < Left , Right | J > ( this . leftJoin , [ ] ) ; for ( let join of this . _joins ) { let joinAnnotations = rightCollection . annotations . filter ( ( rightAnnotation ) => { return filter ( join , rightAnnotation ) ; } ) ; type JoinItem = Record < Left , Annotation < any > > & Record < Right | J , Array < Annotation < any > > > ; ( join as any ) [ rightCollection . name ] = joinAnnotations ; results . push ( join as JoinItem ) ; } return results ; } join < J extends string > ( rightCollection , filter ) { return this . outerJoin ( rightCollection , filter ) . where ( ( record ) => record [ rightCollection . name ] . length > 0 ) ; } where ( filter ) { return new Join < Left , Right > ( this . leftJoin , this . _joins . filter ( filter ) ) ; } push ( join ) { this . _joins . push ( join ) ; } update ( callback ) { this . _joins . forEach ( callback ) ; } }